Return to the table of contents



Taking Inventory

A class Act

Now we have enough of the fundamentals of programming under our belts to look at some of the more powerful features of C++. As I've mentioned before, C++ is the successor to C. What I haven't told you is why it was invented. One of the main reasons was to improve on C's support for user defined data types. What are these, and why are they so important?

As is the case with C++, the data types available in C are divided into two groups: native (i.e., defined in the language itself) and user defined (i.e., defined by the programmer).1 However, there is a major difference between C and C++ in the support provided to user defined types. In C, variables of the native types are fully supported by the language, while variables of user defined types are not; the native types that we've been using are char, short, and unsigned short (and int, but only for the return type of main), all of which have been inherited from C.2 By fully supported, I mean that native variables in both C and C++ can be defined, initialized, assigned values, passed as arguments and return values, and compared to other values of the same type. Such a variable can be assigned storage in either the static or auto storage classes: If a variable is auto, the storage is assigned at entry to the function where it is defined, and released automatically at exit from that function; if it is static, it is initialized to some reasonable value either at link time (for a global variable) or upon the first entry to the function where it is defined (for a local variable).3 However, most of these facilities aren't available to user defined data types in C. For example, they can't be compared; of course, this limitation is understandable, since the compiler has no idea how to compare two variables of a type that you define. Similarly, what is a reasonable default value for a variable of a user defined type? Presumably, the user (i.e., the programmer) knows, but the compiler doesn't.

In this chapter, we'll see how to give the compiler enough information to allow data types that we define to behave just like the native types. Let's start out with some definitions and objectives. Susan had a revealing question here:

Susan: I think I need to find out something here. I am getting the impression that what is "native" is C and what is "user defined" is C++. Is that right? And if so, why?

Steve: Pretty much so. As to why, the answer is pretty simple: the reason that C++ was invented in the first place was to add good support for user defined types to the efficiency of C.

Definitions

A class is a user defined type.

A class interface tells the compiler what facilities the class provides. This interface is usually found in a header file, which by convention has the extension .h.

A class implementation tells the compiler how to implement the facilities defined in the class interface. This is usually found in a source code file, which in the case of the compiler on the CD-ROM in the back of this book usually has the extension .cc.

An object is a variable of a class type. Its behavior is defined by the code that implements the class.

A member function is a function that is part of the definition of a class.

A member variable is a variable that is part of the definition of a class.

Object-oriented programming is the organization of programs as collections of objects, rather than as collections of functions operating on variables of native data types.

Encapsulation is the concept of hiding the details of a class inside the implementation of that class rather than exposing them in the interface. This is one of the primary organizing principles that characterize object-oriented programming.

Objectives of This Chapter

By the end of this chapter, you should

  1. Understand what a user defined type (a class) is, and how it is defined.
  2. Understand how variables of some simple classes are created, destroyed, and copied.
  3. Understand how and why access to the internals of a class is controlled.

Pay Some Attention to the Man Behind the Curtain

In C++, a user defined variable is called an object. Each object has a type, just like variables of native types (short, char, etc.). For example, if we define a class called StockItem (as we will do in this chapter), then an object can be of type StockItem, just as a native variable can be of type short. However, an additional step is required when we want to use user defined types. Since the compiler has no intrinsic knowledge of these types, we have to tell it exactly what they are and how they work. We do this by defining a class, which specifies both the data contained in the user defined variable and what operations can be performed on these data. Here's how Susan reacted upon her first encounter with this idea.

Susan: I can tell that there is only one thing that I think that I understand about this. That is, that C++ is not a language. You have to make it up as you go along. . . .

That may be overdoing it a bit, but there is a grain of truth in her observation: C++ is more of a "language kit" than it is a language. What do I mean by this?

I mean that to use C++ in the most effective way, rather than merely as a "better C", it is necessary to make up data types and tell the compiler how to treat them as though they were native data types. So far in this book, we have been using data types that were previously defined, either by the compiler and language (native types, e.g., short, char) or by libraries (class types, e.g., string). Now we're going to actually make up our own types that will be usable just like those of a native type; the difference between using variables and making up new variable types is analogous to the difference between using a program and writing a program, but carried to the next higher level.

In the event that you find this notion hard to understand, you're not alone; neither did Susan.

Susan: This is an outrage! I didn't understand one other word as I was far beyond anything that could even be described as shock. I think I did faint. I may as well have been in a coma.

Interestingly enough, she did in fact understand this idea of making up our own data types, so perhaps she was overestimating the degree of her shock.

Before we get back to the technical explanation of how we create new data types, I'm sure one more question is burning in your mind: Why should we do this? What's wrong with the native types like char and short? The answer is simple: We make up types so that we can match the language to the needs of the problem we're trying to solve. For example, suppose we want to write a program to do inventory control for a small business like a grocery store. Such a program needs objects representing items in the store, which have prices, names, and so on. We'd need to define each of these types of objects so that it can display the behavior appropriate to the thing it represents. The availability of objects that have relevance to the problem being solved makes it much easier to write (and read) a program to handle inventory than if everything has to be made of shorts and chars.

I suspect that the advantages of making up one's own data types may still not be apparent to you, so let me make an analogy with natural languages. Making up new data types in C++ is in some ways quite similar to making up new words in English (for example). You might think that if everyone made up new words, the result would be chaos. Actually, this is correct, with the very important exception of technical jargon and other vocabularies that are shared by people who have more in common than simply being speakers of English. For example, physicians have their own "language" in the form of medical terminology. Of course, a cynical observer might conclude that the reason for such specialized vocabulary is to befuddle or impress the naive listener, and of course it can be used for that purpose. However, there is also a much more significant and valid reason: To make it possible for experts in a field to communicate with one another quickly and precisely. The same is true of creating our own data types; they enable us to write programs that are more understandable to those who are conversant with the problems being solved. It's much easier to talk to a store owner about inventory objects than about shorts and chars!

Here's the discussion that Susan and I had on this topic:

Susan: Why should we have user defined data types?

Steve: So that you can match the language to the needs of the problem you're trying to solve. For example, if you were writing a nurse's station program in C++, you would want to have objects that represented nurses, doctors, patients, various sorts of equipment, and so on. Each of these objects would display the behavior appropriate to the thing or person it was representing.

Susan: Why do you need that? What if each individual who spoke English made up a unique version of English (well, it is user defined, right?), how can we communicate? This is garbage.

Steve: We need user defined types for the same reason that specialists need jargon in their technical fields. For example, why do you health-care professionals need words like tachycardia? Why don't you just say "a fast heartbeat" in simple English?

Hey, that's not a bad way to explain this: Adding classes is like adding specialized vocabulary to English. I don't remember ever seeing that explanation before; what do you think of it?

Susan: Huh? Then you are saying that, by defining a class of objects, they can take on more realistic qualities than just abstract notions? That is, if I wanted to define nurse in a program, then I would do it with a class named nurse and then I can define in that program the activities and functions that the nurse objects would be doing. Is this how you keep everything straight, and not mix them up with other objects?

Steve: Yes, that's one of the main benefits of object-oriented programming. You might be surprised how hard it is to teach an old-line C programmer the importance of this point.

Susan: So is this what object-oriented programming is? I have heard of it, but never knew what it meant. Could it also be described as user defined programming? I guess there are advantages to teaching a novice; you don't have to undo old ideas to make way for newer ones. So, anything that is user defined is a class? That is, native variables are not classes?

Steve: Every user defined type is a class; data items of a class type are called objects. Variables of native types are not objects in the strict sense.

Susan: OK, so if I want to make up something, then what I make up is called a class as opposed to the other type of stuff that isn't made up and is really C++; that is called native. That is intuitive, thank you. Then the class is made up of data items? And what about native variables; are they objects? I guess just the variables of the class are called objects because I just made an effort to move and read your definition for object. So native variables are not objects, they are just variables. Am I am talking in circles again?

Steve: No, you're not; you're making perfect sense. The only point you have missed is that there are functions in the objects, as well as data items. We'll get into that shortly.

Susan: So Steve, tell me this: What have I been doing up to this point? How does this new stuff compare to the old stuff and which one is it that the programmer really uses? (Let's see, do I want curtain 1 or 3; which one holds the prize?) I just want to get a little sense of direction here; I don't think that is a whole lot to ask, do you?

Steve: What you've been doing up to this point is using classes (string, vector) as well as native types like short and char. This new stuff shows how to create classes like string, rather than just using them.4

Assuming that I've sold you on the advantages of making up our own data types, let's see how we can actually do it. Each data type is represented by a class, whose full definition is composed of two parts: the interface definition (usually contained in a file with the extension .h), and the implementation definition (usually contained in a file with the extension .cc). The interface definition tells the compiler (and the class user) what the class does, while the implementation definition tells the compiler how the objects of that class actually perform the functions specified in the interface definition. Let's take a look at a step-by-step description of how to create and use a class.

  1. Write the class interface definition, which will be stored in a file with the extension .h. In our example of a StockItem class, we'll use item1.h to hold our first version of this interface definition. This definition tells the compiler the names and types of the member functions and variables that make up the objects of the class, which gives the compiler enough information to create objects of this class in a user's program.
  2. Write the class implementation definition, which will be stored in a file with the extension .cc; in our example, the first one of these will be stored in the file item1.cc. This definition is the code that tells the compiler how to perform the operations that the interface definition refers to. The implementation definition file must #include the interface definition file (item1.h, in this case) so that the compiler has access to the interface that is being implemented.
  3. Write the program that uses objects in the class to do some work; the first such program we'll write will be itemtst1.cc. This program also needs to #include the interface definition file, so that the compiler can tell how to create objects of this class.
  4. Compile the class implementation definition to produce an object file (item1.o). This makes the class available for use by the user program.
  5. Compile the user program to produce an object file (itemtst1.o).
  6. Link the object file from the user program, the object file from the class implementation definition, and the standard libraries together to form a finished executable; our first sample will be called itemtst1.exe.

A couple of items in this list need some more discussion. Let's see how Susan brought them to my attention.

Susan: I have a problem here. First under section 2, you put "The class implementation definition file must #include"; excuse me, but that doesn't make sense. What do you mean by #include? How do you say that, "pound include"?

Steve: Yes, that's how it's pronounced. You could also leave off the "pound" and just say "include", and every C and C++ programmer would understand you.

Susan: Section 6 that stuff with the linking. . .isn't that done by the compiler; if not, how do you do it?

Steve: The linker does it, but the compiler is generally capable of calling the linker automatically for you; that's why we haven't needed to worry about this before.

Susan: OK, where is the linker? Is it not part of the compiler software? If not, where does it come from?

Steve: Every compiler comes with one, but you can also buy one separately if you prefer.

Susan: Who puts it in your computer? Also, how do you "call" the linker if you have always had the compiler do it for you?

Steve: It is installed along with the compiler. You can tell the compiler not to call it automatically if you prefer to do it manually; there are reasons to do that sometimes. For example, when you're making a change that affects only one module in a large program, you recompile only that one module, then relink all the object files again to make the executable.

Susan: How do you do that manually?

Steve: This varies according to what compiler you're using. With the DJGPP compiler, you use the compiler to compile one file and then tell the compiler separately to use the linker to link all the object files. If you want to run the linker directly, you can use the gxx command instead of the usual gcc command that runs the compiler.

Taking Stock

Now let's start on our first class definition, which is designed to help solve the problem of maintaining inventory in a small grocery store. We need to keep track of all the items that we carry, so we're going to define a class called StockItem. The StockItem class, like other classes, is composed of a number of functions and variables. As I suggested to Susan earlier, to make this more concrete, think of something like Lego blocks, which you can put together to make parts that can in turn be used to build bigger structures. The smallest Legos are the native types, and the bigger, composite ones are class types.

For the compiler to be able to define an object correctly, we'll have to tell it the names and types of the member variables that will be used to store the information about each StockItem; this enables the compiler to allocate memory for a StockItem. So how do we identify these member variables? By considering what member variables each StockItem object will need to keep track of its corresponding item in the stock of the store. After some thought, I've come up with the following list of member variables:

  1. The name of the item (m_Name),
  2. The number in stock (m_InStock),
  3. The distributor that we purchase it from (m_Distributor),
  4. The price we charge (m_Price), and
  5. The item number, or UPC (m_UPC).

What I mean by an item is actually something like "chunky chicken soup, 16 oz.", rather than a specific object like a particular can of soup. In other words, every can of soup with the same item number is considered equivalent to every other can of soup with the same item number, so all we have to keep track of for each item can be described by the above data. For the item number, we'll use the Universal Product Code (UPC), which is printed as a bar code on almost every product other than fresh produce; it's a 10-digit number, which we'll represent as a string for convenience.

Susan took me to task about the notion of a StockItem object vs. a specific object like a particular can of soup. It didn't take too long to clear this one up:

Susan: When you say "rather than a specific object", how much more specific can you get than "chunky chicken soup, 16 oz."?

Steve: Each can of chunky chicken soup is at least slightly different from every other one; at the very least, they are in different places.

Let's recap what we know about a StockItem so far. We need a member variable in the StockItem class definition for each value in the above description: the name of the item (m_Name), its price (m_Price), the number of items in stock (m_InStock), the name of the distributor (m_Distributor), and the UPC (m_UPC) of the item. Of course, merely storing these data isn't very useful unless we can do something with them. Therefore, objects of the StockItem class also need to be able to perform several operations on their data; we'll start by giving them the ability to display their contents. Figure itemtst1 illustrates a very simple way that this class might be used.

The initial sample program for the StockItem class (code\itemtst1.cc) (Figure native.class)

(Figure itemtst1)
code/itemtst1.cc

This program defines a StockItem named soup, assigns it some data, displays it on the screen via a function called Display, and finally terminates normally.5 By the time we're done with this chapter, you'll understand exactly how every operation in this program is performed by the StockItem class. Before we get too deeply into this particular class, however, we should look at the functions that almost all classes have in common. First, we'll need some more definitions to clarify the terms that we'll need for the discussion.

More Definitions

A concrete data type is a class whose objects behave like variables of native data types. That is, the class gives the compiler enough information that its objects can be created, copied, assigned, and automatically destroyed just as native variables are. The StockItem class that we will construct in this chapter is a concrete data type.

A constructor is a member function that creates new variables of the class type. All constructors have the same name as the class for which they are constructors; therefore, the constructors for StockItem variables also have the name StockItem.

A default constructor is a constructor that is used when no initial value is specified for an object. Because it is a constructor, it has the same name as the class; since it is used when no initial value is specified, it has no arguments. Thus, StockItem() is the default constructor for the StockItem class.

A copy constructor is a constructor that makes a new object with the same contents as an existing object of the same type.

An assignment operator is a member function that sets a pre-existing object to the same value as another object of the same type.

A destructor is a member function that cleans up when an object expires; for a local object, this occurs at the end of the function where that object is defined.

. is the "object member access" operator. It separates an object name, on its left, from the member variable or member function on its right.

:: is the class membership operator, which indicates the class a function belongs to.

Common Behavior

While different classes vary considerably in the facilities that they provide, there are significant benefits to a class whose objects behave like those of native types. As I've just mentioned, such a class is called a concrete data type. To make a class a concrete data type, we must define certain member functions that allow creation, copying, and deletion to behave as with a native variable.

Susan wanted to see a chart illustrating the correspondence between what the compiler does for a native type and what we have to do to supply the same functionality to allow a new data type that we define to behave in the same way as a native data type; that is, to make our new type a concrete data type. Of course, I complied with her request: the result is the list below.

Comparison of native and user-defined types

Here are the essential facilities that the compiler provides for every native type:

1. The ability to create a variable with no specified initial value (an uninitialized variable), e.g., short x;.

2. The ability to pass a variable as an argument to a function; in this case, the compiler has to make a copy of the variable so that the called function doesn't change the value of the variable in the calling function.

3. The ability to assign a value of an appropriate type to a variable that already exists, such as x = 22; or x = z;.

4. Reclaiming the storage assigned to a variable when it ceases to exist, so that those memory addresses can be reallocated to other uses. In the case of auto variables, this is at the end of the function where they were created; with static variables, it's at the end of execution of the program.

To make a concrete data type, we have to provide each of these facilities for our new type. By no coincidence, there is a specific type of member function to provide each of them. Here are the official names and descriptions of each of these four functions:

1. A default constructor that can create an object when there is no initial value specified for the object.

2. A copy constructor that can make a new object with the same contents as an existing object of the same type.

3. An assignment operator that is used to set an existing object to the value of another object of the same type.

4. A destructor that cleans up when an object expires, including releasing the memory that the object has occupied; for a local object, this occurs at the end of the function where the object was created.

Because these member functions are so fundamental to the proper operation of a class, the compiler will generate a version of each of them for us if we don't write them ourselves, just as the corresponding behavior is automatically supplied for the native types. As we will see in Chapter string.htm, the compiler-generated functions are generally too simplistic to be used in a complex class; in such a case we need to create our own versions of these functions. I'll illustrate how to do that at the appropriate time. However, with a simple class such as the one we're creating here, the compiler-generated versions of the assignment operator, copy constructor, and destructor are perfectly adequate, so we won't be creating our own versions of these functions for StockItem. Susan was a bit confused about the distinction between the compiler-generated versions of these essential functions and the compiler's built-in knowledge of the native types:

Susan: Aren't the compiler-generated versions the same thing as the native versions?

Steve: No, they're analogous but not the same. The compiler-generated functions are created only for objects, not for native types. The behavior of the native types is implemented directly in the compiler, not by means of functions.

Susan: I'm confused. Maybe it would help if you explained what you mean by "implemented directly in the compiler". Are you just saying that objects are implemented only by functions, whereas the native types are implemented by the built-in facilities of the compiler?

Steve: You're not confused, you're correct.

Susan: OK, here we go again. About the assignment operator, what is this "version"? I thought you said earlier that if you don't write your own assignment operator it will use the native operator. So I don't get this.

Steve: There is no native assignment operator for any class type; instead, the compiler will generate an assignment operator for a class if we don't do it ourselves.

Susan: Then how can the compiler create an assignment operator if it doesn't know what it is doing?

Steve: All the compiler-generated assignment operator does is to copy all of the members of the right-hand variable to the left-hand variable. This is good enough with the StockItem class. We'll see in Chapter string.htm why this isn't always acceptable.

Susan: Isn't a simple copy all that the native assignment operator does?

Steve: The only native assignment operators that exist are for native types. Once we define our own types, the compiler has to generate assignment operators for us if we don't do it ourselves; otherwise, it would be impossible to copy the value of one variable of a class type to another without writing an assignment operator explicitly.

Susan: OK, this is what confused me, I just thought that the natives would be used as a default if we didn't define our own in the class type, even though they would not work well.

Steve: They will. That's what the compiler-generated assignment operator does. I think we have a semantic problem here, not a substantive one.

Susan: Why doesn't it default to the native assignment operator if it doesn't have any other information to direct it to make a class type operator? This is most distressing to me.

Steve: There isn't any native assignment operator for a StockItem. How could there be? The compiler has never heard of a StockItem until we define that class.

Susan: So it would be a third type of assignment operator. At this point, I am aware of the native type, the user defined type and a compiler-generated type.

Steve: Right. The native type is built into the compiler, the user defined type is defined by. . . the user, and the compiler-generated type is created by the compiler for user defined types where the user didn't define his own.

Susan: Then the native default and the compiler-generated assignment operator are the same? If so, why did you agree with me that there must be three different types of assignment operators? In that case there would really only be two.

Steve: No, there is a difference. Here is the rundown:

  1. (Native assignment) The knowledge of how to assign values of every native type is built into the compiler; whenever such an assignment is needed, the compiler emits prewritten code that copies the value from the source variable to the destination variable.
  2. (Compiler-generated assignment) The knowledge of how to create an assignment operator for any class type is built into the compiler; the compiler generates code for an assignment operator that merely copies all of the members of the source variable to the destination variable. Note that this is slightly different from 1, where the compiler writes the instructions directly into the object file whenever the assignment is done; here, it generates an assignment operator and then uses that operator whenever an assignment is done.
  3. (User defined assignment) This does exactly what we define it to do.

Susan: Did you ever discuss the source variable and the destination variable? I don't recall that concept in past discussions. I like this. All I remember is when you said that = means to set the variable on the left to the value on the right. Does this mean that the variable on the left is the destination variable and the value on the right is the source variable?

Steve: Yes, if the value on the right is a variable; it could also be an expression such as "x + 2".

Susan: But how could it be a variable if it is a known value?

Steve: It's not its value that is known, but its name. Its value can vary at run time, depending on how the program has executed up till this point.

Susan: So the main difference is that in 1 the instructions are already there to be used. In 2 the instructions for the assignment operator have to be "called up" to be used.

Steve: That's a good explanation.

After my explanation of the advantages of a concrete data type, Susan became completely convinced, so much so that she wondered why we would ever want anything else.

Susan: On your definition for concrete data types. . . this is fine, but what I am thinking is that if something wasn't a concrete data type, then it wouldn't work, that is unless it was native. So what would a workable alternative to a concrete data type be?

Steve: Usually, we do want our objects to be concrete data types. However, there are times when, say, we don't want to copy a given object. In the case of an object representing a window on the screen, copying such an object would cause another window to be displayed, which is probably not what we would want to happen.

Susan: OK, so what would you call an object that isn't of a concrete data type?

Steve: There's no special name for an object that isn't of a concrete data type.

Susan: So things that are not of a concrete data type have no names?

Steve: No, they have names; I was just saying that there's no term like non-concrete data type, meaning one that doesn't act like a native variable. There is a term abstract data type, but that means something else.

Susan: See, this is where I am still not clear. Again, if something is not a concrete data type, then what is it?

Steve: There's no special term for a class that doesn't act like a native variable type. If something isn't a concrete data type, then you can't treat it like a native variable. Either you can't copy it, or you can't assign to it, or you can't construct it by default, or you can't destruct it automatically at the end of the function (or some combination of these). The lack of any of those features prevents a class from being a concrete data type.

Susan: Of what use would it be to have a class of a non-concrete data type? To me, it just sounds like an error.

Steve: Sometimes it does make sense. For example, you might want to create a class that has no default constructor; to create an element of such a class, you would have to supply one or more arguments. This is useful in preventing the use of an object that doesn't have any meaningful content; however, the lack of a default constructor does restrict the applicability of such a class, so it's best to provide such a constructor if possible.

Before we can implement the member functions for our StockItem class, we have to define what a StockItem is in more detail than my previous sketch.6 Let's start with the simplified version of the interface specification for that class in Figure item1h, which includes the specification of the default constructor, the display function, and another constructor that is specific to the StockItem class.

I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this part of the chapter; those files are item1.h, item1.cc, and itemtst1.cc, respectively.

code/item1.h

The initial interface of the StockItem class (code\item1.h) (Figure item1h)

Your first reaction is probably something like "What a bunch of malarkey!" Let's take it a little at a time, and you'll see that this seeming gibberish actually has a rhyme and reason to it. First we have the line class StockItem. This tells the compiler that what follows is the definition of a class interface, which as we have already seen is a description of the operations that can be performed on objects of a given user defined type;7 in this case, the type is StockItem. So that the compiler knows where this description begins and ends, it is enclosed in {}, just like any other block of information that is to be treated as one item.

After the opening {, the next line says public:. This is a new type of declaration called an access specifier, which tells the compiler the "security classification" of the item(s) following it, up to the next access specifier. This particular access specifier, public, means that any part of the program, whether it is defined in this class or not, can use the items starting immediately after the public declaration, and continuing until there is another access specifier.8 In the current case, all of the items following the public specifier are operations that we wish to perform on StockItem objects. Since they are public, we can use them anywhere in our programs. You may be wondering why everything isn't public; why should we prevent ourselves (or users of our classes) from using everything in the classes? It's not just hardheartedness; it's actually a way of improving the reliability and flexibility of our software, as I'll explain later.

As you might imagine, this notion of access specifiers didn't get past Susan without a battle. Here's the blow-by-blow account.

Susan: So, is public a word that is used often or is it just something you made up for this example?

Steve: It's a keyword of the C++ language, which has intrinsic meaning to the compiler. In this context, it means "any function, inside or outside this class, can access the following stuff, up to the next access specifier (if any)." Because it is a keyword, you can't have a variable named public, just as you can't have one named if.

Susan: These access specifiers: What are they, anyway? Are they always used in classes?

Steve: Yes.

Susan: Why aren't they needed for native variables?

Steve: Because you can't define access specifiers for native types; their interface and implementation are all predefined in the compiler.

Susan: What does internals mean? Do you mean stuff that is done by the compiler rather than stuff that can be done by the programmer?

Steve: Yes, in the case of native data types. In the case of class types, internals means the details of implementation of the type rather than what it does for the user.

Susan: You know, I understand what you are saying about internals; that is, I know what the words mean, but I just can't picture what you are doing when you say implementation. I don't see what is actually happening at this point.

Steve: The implementation of a class is the code that is responsible for actually doing the things that the interface says the objects of the class can do. All of the code in the item1.cc file is part of the implementation of StockItem. In addition, the private member variables in the header file are logically part of the implementation, since the user of the class can't access them directly.

Susan: Why is a class function called a member function? I like class function better; it is more intuitive.

Steve: Sorry, I didn't make up the terminology. However, I think member function is actually more descriptive, because these functions are members (parts) of the objects of the class.

Susan: So on these variables, that m_ stuff; do you just do that to differentiate them from a native variable? If so, why would there be a confusion, since you have already told the compiler you are defining a class? Therefore, all that is in that class should already be understood to be in the class rather than the native language. I don't like to look at that m_ stuff; it's too cryptic.

Steve: It's true that the compiler can tell whether a variable is a member variable or a global variable. However, it can still be useful to give a different name to a member variable so that the programmer can tell which is which. Remember, a member variable looks like a global variable in a class implementation, because you don't declare it as you would an argument or a local variable.

Now we're up to the line that says StockItem();. This is the declaration for a function called a constructor, which tells the compiler what to do when we define a variable of a user defined type, since as we have already seen, there's no way for it to know this otherwise. This particular constructor is the default constructor for the StockItem class. It's called the "default" constructor because it is used when no initial value is specified by the user; the empty parentheses after the name of the function indicate the lack of arguments to the function. The name of the function is the clue that it's a constructor; the name of a constructor is always the same as the name of the class for which it's a constructor, to make it easier for the compiler to identify constructors among all of the possible functions in a class.

This idea of having variables and functions "inside" objects wasn't intuitively obvious to Susan:

Susan: Now, where you talk about mixing a string and a short in the same function, can this not be done in the native language?

Steve: It's not in the same function but in the same variable. We are creating a user defined variable that can be used just like a native variable.

Susan: OK, so you have a class StockItem. And it has a function called StockItem. But a StockItem is a variable, so in this respect a function can be inside a variable?

Steve: Correct. A StockItem is a variable that is composed of a number of functions and other variables.

Susan: OK, I think I am seeing the big picture now. But you know that this seems like such a departure from what I thought was going on before, where we used native types in functions rather than the other way around. Like when I wrote my little program, it would have shorts in it but they would be in the function main. So this is a complete turnabout from the way I used to think about them; this is hard.

Steve: Yes, that is a difficult transition to make. Interestingly enough, experience isn't necessarily an advantage here; you haven't had as much trouble with it as some professional programmers who have a lot more experience in writing functions as "stand-alone" things with no intrinsic ties to data structures. However, it is one of the essentials in object-oriented programming; most functions live "inside" objects, and do the bidding of those objects, rather than being wild and free.

Why do we need to write our own default constructor? Well, although we have already specified the member variables used by the class, so that the compiler can assign storage as with any other static or auto variable, that isn't enough information for the compiler to know how to initialize the objects of the class correctly.9 Unlike a native variable, the compiler can't set a newly created StockItem to a reasonable value, since it doesn't understand what the member variables of a StockItem are used for. That is, it can't do the initialization without help from us. In the code for our default constructor, we will initialize the member variables to legitimate values, so that we don't have to worry about having an uninitialized StockItem lying around as we did with a short in a previous example. Figure itemdefaultcons shows what the code to our first default constructor looks like.

The default constructor for the StockItem class (from code\item1.cc) (Figure itemdefaultcons)

StockItem::StockItem()

: m_InStock(0), m_Price(0), m_Name(), m_Distributor(), m_UPC()

{

}

Let's use this example of a StockItem class to illuminate the distinction between interface and implementation. As I've already mentioned, the implementation of a class is the code that is responsible for actually doing the things promised by the interface of that class. The interface was laid out in Figure item1h. With the exception of the test program that illustrates the use of the StockItem class, all of the code that we will examine in this chapter is part of the implementation: This includes the constructors and the Display member function.

So you can keep track of where this fits into the "big picture", the code in Figure itemdefaultcons is the implementation of the function StockItem::StockItem() (i.e., the default constructor for the class StockItem), whose interface was defined in Figure item1h. Now, how does it work? Actually, this function isn't all that different from a "regular" function, but there are some important differences. First of all, the name looks sort of funny: Why is StockItem repeated?

The answer is that, unlike "regular" (technically, global) functions, a member function always belongs to a particular class. That is, such a function has special access to the data and other functions in the class. To mark its membership, its name consists of the name of the class (in this case, StockItem), followed by the class membership operator ::, followed by the name of the function (which in this case, is also StockItem); as we have already seen, the name of a constructor is always the same as the name of its class. Figure itemdefaultconsdecoder shows how each component of the function declaration contributes to the whole.10

The declaration of the default constructor for the StockItem class (Figure itemdefaultconsdecoder)

This function belongs to the StockItem class;                
                
      |  it is a constructor, because its name is the                 
      |  same as the name of the class;                  
      |                
      |            |   and it has no arguments.                 
      |            |           In other words, it is the                
      |            |       |   default constructor for                
      |            |       |   its class.                         
      |            |       |                        
      |            |       |                        
      |            |       |                        
      |            |       |                                  
+-----+------++----+----+ ++-+                 
 StockItem ::  StockItem   ()

If you've really been paying attention, there's one thing that you may have noticed about this declaration as compared with the original declaration of this function in the class interface definition for StockItem (Figure item1h). In that figure, we declared this same function as StockItem();, without the additional StockItem:: on the front. Why didn't we need to use the StockItem:: class membership notation in the class interface definition? Because inside the declaration of a class, we don't have to specify what class the member functions belong to; by definition, they belong to the class we're defining. Thus, StockItem() in the class interface declaration means "the member function StockItem, having no arguments"; i.e., the default constructor for the StockItem class.

Susan didn't have any trouble with this point, which was quite a relief to me, as I was dreading a big argument.

Susan: Oh, so you don't have to write StockItem::StockItem in the interface definition because it is implied by the class StockItem declaration?

Steve: Right.

Now let's look at the part of the constructor that initializes the member variables of the StockItem class, the member initialization list. The start of a member initialization list is signified by a : after the closing ) of the constructor declaration, and the expressions in the list are separated by commas; it can be used only with constructors, not any other type of functions.

The member initialization list of the default StockItem constructor is: : m_InStock(0), m_Price(0), m_Name(), m_Distributor(), m_UPC(). What does this mean exactly? Well, as its name indicates, it is a list of expressions each of which initializes one member variable. In the case of a native variable type, it is equivalent to creating the variable with the initial value specified in the parentheses. In the case of a class type, it is equivalent to creating the variable by calling the constructor that matches the type(s) of argument(s) specified in the parentheses, or the default constructor if there are no arguments specified. So the expression m_InStock(0) is analogous to the creation of a local variable by the statement short m_InStock = 0;, and the expression m_Name() is analogous to the creation of a local variable by the statement string m_Name;, which initializes the string m_Name to the empty C string "".

Using a member initialization list is the best way to set up member variables in a constructor, for two reasons. First, it's more efficient than using assignment statements to set the values of member variables. For example, suppose that we were to write this constructor as shown in Figure itemolddefaultcons.

Another way to write the default StockItem constructor (Figure itemolddefaultcons)

StockItem::StockItem()

{

m_InStock = 0;

m_Price = 0;

m_Name = "";

m_Distributor = "";

m_UPC = "";

}

If we wrote the constructor that way, before we got to the opening { of the constructor, all of the member variables that had constructors (here, the strings) would be initialized to their default values. After the {, they would be set to the values we specified in the code for the constructor. It's true that we could solve this problem in this specific example by simply not initializing the strings at all, as that would mean that they would be initialized to their default values anyway, that solution wouldn't apply in other constructors such as the one in Figure itemnormalcons, where the member variables have specified values rather than default ones.

The second reason that we should use a member initialization list to initialize our member variables is that some member data items aren't variables at all but constants. We'll see how to define consts, as they are called in C++, in a later chapter. For now, it's enough to know that you can't assign a value to a const, but you can (and indeed have to) initialize it; therefore, when dealing with member consts, a member initialization list isn't just a good idea, it's the law.

There is one fine point that isn't obvious from looking at the code for this constructor: The expressions in a member initialization list are executed in the order in which the member variables being initialized are declared in the class definition, which is not necessarily the order in which the expressions appear in the list. In our example, since m_InStock appears before m_Name in the class definition, the member initialization expression for m_InStock will be executed before the expression initializing m_Name. This doesn't matter right now, but it will be important in Chapter string.htm, where we will be using initialization expressions whose order of execution is important.

You may have noticed that the executable part of the function (Figure itemdefaultcons) is empty, because all of the work has already been done by the member initialization list. This is fairly common, but not universal; as we'll see in Chapter string.htm, sometimes a constructor has to do something other than initialize member variables, in which case we need some code inside the {}.

Susan had an objection to my cavalier use of the empty C string "":

Susan: Excuse me, but what kind of value is " " ? Do you know how annoying it is to keep working with nothing?

Steve: It's not " ", but "". The former has a space between the quotes, and the latter does not; the former is a one-character C string consisting of one space, while the latter is a zero-character C string.

Susan: OK, so the "" is an empty C string, but could you please explain how this works?

Steve: The "" means that we have a C string with no data in it. The compiler generates a literal C string consisting of just the terminating null byte.

Susan: What good does that do? I don't get it.

Steve: Well, a string has to have some value for its char* to point to; if we don't have any real data, then using an empty C string for that purpose is analogous to setting a numeric value to 0.

Susan: OK, so this is only setting the strings in the default constructor to a value that the compiler can understand so you don't get an error message, although there is no real data. We're trying to fool the compiler, right?

Steve: Close, but not quite. Basically, we want to make sure that we know the state of the strings in a default StockItem. We don't want to have trouble with uninitialized variables; remember how much trouble they can cause?

Susan: Yes, I remember. So this is just the way to initialize a string when you don't know what real value it will end up having?

Steve: Right.

Now let's get back to the member variables of StockItem. One important characteristic of any variable is its scope, so we should pay attention to the scope of these variables. In Chapter function.htm, we saw two scopes in which a variable could be defined: local (i.e., available only within the function where it was defined) and global (i.e., available anywhere in the program). Well, these variables aren't arguments (which have local scope) since they don't appear in the function's header, and they aren't defined in the function; therefore, they aren't local variables. Surely they can't be global variables, after I showed you how treacherous those can be.

Go to the Head of the class

I haven't misled you on that point; there is another scope called class scope, which applies to all member variables of a class.11 Class scope means that each object of a given class has one set of member variables. In the case of StockItem, this set of variables consists of m_InStock, m_Price, m_Name, m_Distributor, and m_UPC. Member functions of a class can access member variables of that class without defining them, as though they were global variables.

In addition to scope, each member variable has another attribute we have already encountered: an access specifier. The access of nonmember functions to any member variable or member function depends on the access specifier in effect when the member variable or function was defined. If you look back at Figure item1h, you'll see that the line private: precedes the definition of the member variables in the StockItem class. The keyword private is an access specifier, like public; however, where a public access specifier allows any function to access the items that follow it, a private access specifier allows only member functions to access items that follow it.12 Susan had some more questions about access specifiers, including this new one, private:

Susan: It seems to me that the access specifiers act more like scope than anything. Are they about the same?

Steve: Yes, the difference between public and private is somewhat analogous to the difference between global and local variables, but the latter distinction affects where a variable is stored and when it is initialized, whereas an access specifier controls what functions can access the variable. However, because member variables are defined inside classes, they can't be global, nor can they be local in the sense that a "regular" (i.e., nonmember) variable can be; a member variable must always live inside a single occurrence of an object of its class.

Susan: Are they necessary for every class?

Steve: Pretty much. The default specifier for a class is private; that is, everything you declare in a class interface before the first explicit access specifier is private. Of course, this also means that if you don't ever provide an explicit access specifier in a given class, then everything declared in that class will be private. This isn't usually very useful, because without any public functions it's hard to use a class at all.

Susan: There is a default? Then why would the default be the least useful?

Steve: To make the programmer specify what should be publicly accessible rather than have it happen automatically. In general, it's best to keep as much as possible private, to reduce the dependency of external code on the internal implementation of the class. This makes it easier to change that implementation without causing trouble for the users of the class.

Susan: OK, that makes sense now. Are there any other kinds of access specifiers or are these the only two?

Steve: Actually, there's one more, but it applies only in cases that we won't get to.

Susan: OK, it's good to know that there won't be any more surprises later on.

Steve: I aim to please.

Susan also wanted some more details about this new class scope.

Susan: How about explaining the difference between a class scope and a public access specifier?

Steve: Variables declared in a class always have class scope; that means that they live as long as the object that contains them. The access specifier determines who can access these variables, but does not affect their lifetime.

Of course, the constructor StockItem::StockItem(), by virtue of being a member function, has access to all member variables, so the private access specifier doesn't apply to it. We'll see later how that access specifier comes into play.

Now that we know what kind of variables the StockItem::StockItem() function deals with, its behavior isn't very mysterious: it simply initializes the member variables to 0 or "", whichever is appropriate to their types. That's all very well, but it doesn't answer a very important question: What exactly do these member variables do? The answer is that they don't do anything by themselves; rather, they are the "raw material" the member functions use to implement the behavior that we want a StockItem to display. If you recall the discussion of interface vs. implementation, then you'll appreciate that the private member variables are also essentially part of the implementation and not part of the interface: Even though they are defined in the header file, the user of the class can't access them directly. That's why we call variables that are declared inside the class definition member variables, and functions that are declared inside the class definition member functions; They "belong" to the class that we're defining. The member functions set, change, and use the values of the member variables in the course of implementing the behaviors that the StockItem class interface definition promises.13 Susan wasn't buying all this malarkey about member variables without a fight. Here's how it went.

Susan: What do m_InStock and m_Price and the others actually do? It seems we are missing a verb here.

Steve: They don't do anything by themselves. They are the member variables used to store the count and price of the goods described by a StockItem object, respectively. In the default constructor, they are both set to 0, indicating a StockItem with no content. This is the equivalent of the value 0 that is used to initialize statically allocated numeric variables and is used in the same way; that is, any StockItem that is created without a value is set to this empty state. Notice that this doesn't apply only to statically allocated StockItems, but to all StockItems; this is an example where a user defined type is superior to a native type. That is, we don't have to worry about having an uninitialized StockItem, because the default constructor ensures that every StockItem is set to a known value when it is created.

Susan: Ugh. Don't remind me about uninitialized variables. Okay, that makes more sense now.

So much for the "high-altitude" description of what a class does. Now let's get back to the details that make it work, starting with a little puzzle: figuring out where the StockItem::StockItem() function is used in the test program in Figure itemtst1. Believe it or not, this constructor is actually used in that program: To be exact, the line StockItem soup; calls it. Remember that the basic idea of constructing a class is to add data types to the language that aren't available "out of the box". One of the functions that we have to help the compiler with is initialization; a main purpose for the StockItem::StockItem() constructor is to initialize variables of the StockItem type that have no values explicitly assigned to them. That's why it's called a default constructor.

Susan didn't immediately cotton to the idea of calling a default constructor by simply defining a variable of that class.

Susan: Sure, defining an object is simple if you don't lose your mind defining the classes first.

Steve: It is simple for the application programmer (the user of the class). We're doing the hard part so he can just use the objects without having to worry about any of this stuff.

Susan: Huh? Isn't the the "user of the class" always the same as the "writer of the class"?

Steve: Not necessarily. You've been using strings (and vectors, for that matter) for some time now without having to be concerned about how they work. This is not unusual.

Susan: Yeah, but if you are a programmer you will be a class writer, not just a user.

Steve: Not necessarily, and certainly not with respect to all the classes. You may very well write your own application-specific classes but use the ones from the standard library for all of the low-level stuff like vectors, strings, etc.

You should generally write a default constructor for every class you define, to guarantee the state of any "default constructed" variable. If you don't define a default constructor, the compiler will supply one for you; however, since it doesn't know much about your class, it won't be able to guarantee very much about the initial state of one of your variables. The moral is that you should define your own default constructor. As you can see from our example, it's not much work.

So why did I say "generally", rather than "always"? Because there are some times when you don't want to allow an object to be created unless the "real" data for it are available. As with the copy constructor, the compiler will generate a default constructor for you automatically; to prevent this, you can declare a private default constructor, which will cause a compiler error in any user code that tries to define an object of that class without specifying any initial values. You don't have to implement this private constructor, because a program that tries to use it will fail in the compile stage and thus will never be linked.

Susan thought that the idea of having to define a default constructor for each class was a bit off the wall.

Susan: When you say that "you should define one of these (default constructors) for every class you define..." my question is how? What are you talking about? I thought a default meant just that, it was a default, you don't have to do anything with it, it is set to a preassigned value.

Steve: It's true that the class user doesn't have to do anything with the default constructors. However, the class writer (that's us, in this case) has to define the default constructor, so when the class user defines an object without providing an initial value, the new object has a reasonable state for an "empty" object. This prevents the problems with uninitialized variables that we've seen crop up with native types.

Shop till You Drop

Now let's continue with our analysis of the class interface (Figure item1h, on page ). Before we can do anything with an inventory record, we have to enter the inventory data. This means that we need another constructor that actually sets the values into the object. We also need some way to display the data for a StockItem on the screen, which means writing a Display function.

The next line of that figure is the declaration of the constructor that creates an object with actual data:

StockItem(string Name, short InStock, short Price, string Distributor, string UPC);

We can tell that this function is a constructor because its name, StockItem, is the same as the name of the class. If you're a C programmer, you may be surprised to see two functions that have the same name, differing only in the types of their arguments. This is not legal in C, but it is in C++; it's called function overloading, and as you'll see, it's a very handy facility that isn't limited to constructors. The combination of the function name and argument types is called the signature of a function; two functions that have the same name but differ in the type of at least one argument are distinct functions.14 In the case of the default constructor, there are no arguments, so that constructor is used where no initial data are specified for the object. The statement StockItem soup; in the sample program (Figure itemtst1) fits that description, so the default constructor is used. However, in the next statement of the sample program, we have the expression:

StockItem("Chunky Chicken",32,129,"Bob's Distribution","123456789");

This is clearly a call to a constructor, because the name of the function is the name of a class, StockItem. Therefore, the compiler looks for a constructor that can handle the set of arguments in this call, and finds:

StockItem(string Name, short InStock, short Price, string Distributor, string UPC);

The first, fourth, and fifth arguments to the constructor are strings, while the second and third are shorts. Since these types all match those specified in the expression in the sample program, the compiler can translate that expression into a call to this constructor.

Figure itemnormalcons shows the code for that constructor.

Another constructor for the StockItem class (from code\item1.cc) (Figure itemnormalcons)

StockItem::StockItem(string Name, short InStock, 

short Price, string Distributor, string UPC)

: m_Name(Name), m_InStock(InStock), m_Price(Price),

m_Distributor(Distributor), m_UPC(UPC)

{

}

As you can see, nothing about this constructor is terribly complex; it merely uses the member initialization list to set the member variables of the object being constructed to the values of their corresponding arguments.

But why do we need more than one constructor? Susan had that same question, and I had some answers for her.

Susan: How many constructors do you need to say the same thing?

Steve: They don't say exactly the same thing. It's true that every constructor in the StockItem class makes a StockItem; however, each argument list varies. The default constructor makes an empty StockItem and therefore doesn't need any arguments, whereas the constructor StockItem::StockItem(string Name, short InStock, short Price, string Distributor, string UPC) makes a StockItem with the values specified by the Name, InStock, Price, Distributor, and UPC arguments in the constructor call.

Susan: Are you saying that in defining a class you can have two functions that have the same name, but they are different in only their arguments and that makes them unique?

Steve: Exactly. This is the language feature called function overloading.

Susan: So StockItem soup; is the default constructor in case you need something that can create uninitialized objects?

Steve: Not quite; the default constructor for the StockItem class is StockItem::StockItem(), which doesn't need any arguments, because it constructs an empty StockItem. The line StockItem soup; causes the default constructor to be called to create an empty StockItem.

Susan: And the line StockItem("Chunky Chicken",32,129,"Bob's Distribution","123456789"); is a constructor that finally gets around to telling us what we are trying to accomplish here?

Steve: No, that line causes a StockItem with the specified contents to be created, by calling the constructor StockItem::StockItem(string Name, short InStock, short Price, string Distributor, string UPC);.

Susan: So are you saying that for every new StockItem you have to have a new constructor for it?

Steve: No, there's one constructor for each way that we can construct a StockItem. One for situations where we don't have any initial data (the default constructor), one for those where we're copying one StockItem to another (the compiler-generated copy constructor), and one for those where we are supplying the data for a StockItem. There could be other ones too, but those are all we have right now.

Once that expression has been translated, the compiler has to figure out how to assign the result of the expression to the StockItem object called soup, as requested in the whole statement:

soup = StockItem("Chunky Chicken",32,129,"Bob's Distribution","123456789");

Since the compiler has generated its own version of the assignment operator = for the StockItem class, it can translate that part of the statement as well, which results in the StockItem object named soup having the value produced by the constructor.

Finally, we have the line soup.Display(); which displays the value of soup on the screen. Figure itemdisplay shows the code for that function.

Display member function for the StockItem class (from code\item1.cc) (Figure itemdisplay)

void StockItem::Display()

{

cout << "Name: ";

cout << m_Name << endl;

cout << "Number in stock: ";

cout << m_InStock << endl;

cout << "Price: ";

cout << m_Price << endl;

cout << "Distributor: ";

cout << m_Distributor << endl;

cout << "UPC: ";

cout << m_UPC << endl;

}

This is also not very complicated; it just uses << to copy each of the parts of the StockItem object to cout, along with some identifying information that makes it easier to figure out what the values represent.

Susan wanted to know how we could use << without defining a special version for this class.

Susan: Hey, how come you don't have to define << as a class operator? Does the compiler just use the native <<? And that works OK?

Steve: We're using << only for types that already have it defined, which includes all of the native types, as well as the one I provided as part of the string class. If we wanted to apply << to a StockItem, we'd have to write our own version; you'll see how we do that when we go into the implementation of string.

Susan: Then please explain to me why << is being used in Figure itemdisplay, which is for the StockItem class.

Steve: It's being used for strings and shorts, not objects of the StockItem class. The fact that the strings and shorts are inside the StockItem class is irrelevant in this context; they're still strings and shorts, and therefore can be displayed by the << operators that handle strings and shorts.

Susan: So the stuff you get out of the standard library is only for the use of class types? Not native?

Steve: The iostreams library is designed to be able to handle both native types and class types; however, the latter use requires the class writer to do some extra work, as we'll see when we add these functions to the string class in Chapter downpath.htm.

Susan: So that means that the library is set up to understand things we make up for our class types?

Steve: That's right; if we follow the library's rules when creating our own versions of << and >>, then we'll be able to use those operators to read and write our data as though the ability to handle those types was built into the library.

That should clear up most of the potential problems with the meaning of this Display function. However, it does contain one construct that we haven't seen before: void. This is the return type of the Display function, as might be apparent from its position immediately before the class name StockItem. But what sort of return value is a void? In this context, it means simply that this function doesn't supply a return value at all.

You won't be surprised to learn that Susan had a few questions about this idea of functions that return no value.

Susan: How can a function not return a value? Then what is the point? Then would it "have no function"?

Steve: Now you're telling programming jokes? Seriously, though, the point of calling a function that returns no value is that it causes something to happen. The Display function is one example; it causes the value of a StockItem object to be displayed on the screen. Another example is a "storage function"; calling such a function can cause it to modify the value of some piece of data it is maintaining, so when you call the corresponding "retrieval function", you'll get back the value the "storage function" put away. Such lasting effects of a function call (other than returning a value) are called side effects.

Susan: But even a side effect is a change, so then it does do something after all, right?

Steve: Sure, it does something; every function should do something, or you wouldn't write (or call) it. However, some functions don't return any value to the calling program, in which case we specify their return type as void.

That takes care of the public part of the class definition. Now what about the private part? As I mentioned before in the discussion of how a class is defined, the access specifier private means that only member functions of the class can access the items after that specifier. It's almost always a good idea to mark all the member variables in a class as private, for two reasons.

  1. If we know that only member functions of a class can change the values of member data, then we know where to look if the values of the data are incorrect. This can be extremely useful when debugging a program.
  2. Marking member variables as private simplifies the task of changing or deleting those member variables should that become necessary. You see, if the member variables are public, then we have no idea what functions are relying on their values. That means that changing or deleting these member variables can cause havoc anywhere in the system. Allowing access only by member functions means that we can make changes freely as long as all of the member functions are kept up to date.

Both of these advantages of keeping member variables private can be summed up in the term encapsulation, which means "hiding the details inside the class implementation rather than exposing them in the interface". This is one of the primary organizing principles that characterizes object-oriented programming.

Now that we've covered all of the member functions and variables of the StockItem class, Figure item1h2 shows the interface for the StockItem class again. As noted previously, the test program for this class, itemtst1.cc, is shown in Figure itemtst1.

The initial interface of the StockItem class (code\item1.h) (Figure item1h2)

code/item1.h

There's only one more point about the member variables in the StockItem class that needs clarification; surely the price of an object in the store should be in dollars and cents, and yet we have only a short to represent it. As you know by now, a short can hold only a whole number, from -32768 to 32767. What's going on here?

Only that I've decided to store the price in cents rather than dollars and cents. That is, when someone types in a price, I'll assume that it's in cents, so "246" would mean 246 cents, or $2.46. This would of course not be acceptable in a real program, but for now it's OK.

This allows prices up to $327.67 (as well as negative numbers for things like coupons), which should be acceptable for a grocery store. In Appendix , I'll give you some tips on how to solve that problem by using a different kind of numeric variable that can hold a greater variety of values. For now, though, let's stick with the short. Figure item1cc shows the implementation for the StockItem class.

The initial implementation of the StockItem class (code\item1.cc) (Figure item1cc)

code/item1.cc

Susan had a few questions about the way that we specified the header files in this program:

Susan: Where did the #include "string6.h come from?

Steve: I wrote it; it's the final version of the string header file as defined in Chapter downpath.htm.

Susan: So then it has not been covered up to this point? Then you might have to explain how it mysteriously appears in this code.

Steve: I'll tell the reader to expect a nauseatingly detailed description later on.

Susan: Oh, and how come this and the #include item1.h are in "" instead of <>? I thought all header files are in <>?

Steve: Putting an include file name in "" tells the compiler to start looking for the file in the current directory, and then search the "standard places"; using the <> tells the compiler to skip the current directory and start with the "standard places"; that is, where the header files are that come with the compiler. What the "standard places" are depends on the compiler.

Susan: I know I have not been the most focused test reader in the last week, but did I miss something here? Did you explain way back in an earlier chapter and I just forgot? I want to make sure that you have explained this in the book, and even if you had earlier it would not be a bad idea to remind the reader about this.

Steve: No, I believe it was my omission. Actually, you've been quite focused, although not in quite the same area.

Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this program. First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE itemtst1, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type itemtst1 to run the program. You'll see that it indeed prints out the information in the StockItem object.

To run it under the debugger, make sure you are in the code subdirectory, and then type "RHIDE itemtst1". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger.

That's good as far as it goes, but how do we use this class to keep track of all of the items in the store? Surely we aren't going to have a separately named StockItem variable for each one.

Vectoring In

This is another application for our old friend the vector; specifically, we need a vector of StockItems to store the data for all the StockItems in the store. In a real application we would need to be able to vary the number of elements in the vector, unlike our previous use of vectors. After all, the number of items in a store can vary from time to time. However, in our example program we'll ignore this complication and just use a vector that can hold 100 StockItems. Even with this limitation, we will have to keep track of the number of items that are in use, so that we can store each new StockItem in its own vector element and keep track of how many items we may have to search through to find a particular StockItem. Finally, we need something to read the data for each StockItem from the inventory file where it's stored when we're not running the program. Susan had some questions about these details of the test program:

Susan: In the paragraph when you are talking about the number of items, I am a little confused. That is, do you mean the number of different products that the store carries or the quantity of an individual item available in the store at any given time?

Steve: The number of different products, which is the same as the number of StockItems. Remember, each StockItem represents any number of objects of that exact description.

Susan: So what you're referring to is basically all the inventory in the store at any given period of time?

Steve: Exactly.

Susan: What do you mean by "need something to read the data" and "where it's stored when we're not running the program." I don't know what you are talking about, I don't know where that place would be.

Steve: Well, where are the data when we're not running the program? The disk. Therefore, we have to be able to read the information for the StockItems from the disk when we start the program.

Susan: Okay, that makes sense now.

Steve: I'm glad to hear it.

Figure itemtst2 is a little program that shows the code necessary to read the data for the StockItem vector into memory when the program starts up.

Reading and displaying a vector of StockItems (code\itemtst2.cc) (Figure itemtst2)

code/itemtst2.cc

This program has a number of new features that need examination. First, we've had to add the "file stream" header file fstream.h to the list of include files, so that we will be able to read data in from a file. The way we do this is to create an ifstream object that is "attached" to a file when the object is constructed. In this case, the line ifstream ShopInfo("shop2.in"); creates an ifstream object called ShopInfo, and connects it to the file named shop2.in.

The next line is AllItems[i].Read(ShopInfo);, which calls the function Read for the ith StockItem in the AllItems vector, passing the ShopInfo ifstream object to a new StockItem member function called Read, which uses ShopInfo to get data from the file and store it into its StockItem (i.e., the ith element in the vector).

This whole process was anything but obvious to Susan.

Susan: How does just adding the header file fstream.h, enable you to read data in from a file?

Steve: The file fstream.h contains the declaration of the ifstream class.

Susan: Where did the ifstream class come from?

Steve: From the standard library that comes with the compiler.

Susan: Where did it come from and how did it get there? Who defined it and when was it written? Your only reference to this is just "The way we do this is to create an ifstream object that is `attached' to a file when the object is constructed". If this is just something that you wrote to aid this program that we don't have to worry about at this time, then please mention this.

Steve: I didn't write it, but we don't have to worry about it. I'll explain it to the minimum extent necessary.

After that bit of comic relief, let's get back to reality. Figure itemread is the implementation of the new Read function:

The Read function for the StockItem class (from code\item2.cc) (Figure itemread)

void StockItem::Read(ifstream& s)

{

s >> m_Name;

s >> m_InStock;

s >> m_Price;

s >> m_Distributor;

s >> m_UPC;

}

As you can see, the Read function is pretty simple; it just reads the data for one StockItem in from the file by way of the ifstream object s, using one >> expression to read each member variable's value. However, there's one construct here we haven't seen before: the & in ifstream&. As soon as we take a look at the new interface to the StockItem class (Figure item2h), we'll see exactly what that means. I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this part of the chapter; those files are item2.h, item2.cc, and itemtst2.cc, respectively.

The second version of the interface for the StockItem class(code\item2.h) (Figure item2h)

code/item2.h

The &, as used here in the context of declaring an argument to a function, means that the argument to which it refers is a reference argument, rather than a "normal" argument. It's important to understand this concept thoroughly, so let's go into it in detail.

References Required

As you may recall from Chapter function.htm, when we call a function, it doesn't ordinarily operate on actual variables in the calling function; instead, a new local variable is created and initialized to the value of each expression from the calling function and the called function works on that local variable. Such a local variable is called a value argument, because it is a new variable with the same value as the caller's original argument. There's nothing wrong with this in many cases; sometimes, though, as in the present case, we have to do it a bit differently. A reference argument, such as the ifstream& argument to Read, is not a copy of the caller's argument, but another name for the actual argument passed by the caller.

Reference arguments are more efficient than value arguments, because the overhead of making a copy for the called function is avoided. Another difference is that any changes made to a reference argument change the caller's argument as well, which in turn means that the caller's actual argument must be a variable, not an expression like x + 3; changing the value of such an expression wouldn't make much sense. This characteristic of reference arguments can confuse the readers of the calling function; there's no way to tell just by looking at the calling function that some of its variables can be changed by calling another function. This means that we should limit the use of reference arguments to those cases where they are necessary.

In this case, however, it is necessary to change the ifstream object that is the actual argument to the Read function, because that object contains the information about what data we've already read from the stream. If we passed the ifstream as a value argument, then the internal state of the "real" ifstream in the calling function wouldn't be altered to reflect the data we've read in our Read function, so every time we called Read, we would get the same input again. Therefore, we have to pass the ifstream as a reference argument.

The complete decoding of the function declaration void StockItem::Read(ifstream& s) is shown in Figure stockitemdecode.

The declaration of the function StockItem::Read in code\item2.h (Figure stockitemdecode)

This means that the function we're defining doesn't return anything;                
                
  |  it belongs to the StockItem class;                  
  |                
  |        |   its name is Read;                                   
  |        |                
  |        |          |   and its argument is a reference to                 
  |        |          |   an ifstream.                
  |        |          |                    
  |        |          |        |                  
  |        |          |        |                  
  |        |          |        |                  
  |        |          |        |                  
 ++-+  +---+-----+   ++-+   +--+----+                
 void  StockItem::   Read  (ifstream& s)

Putting it all together: we're defining a void function (one that doesn't return a value), called Read, which belongs to class StockItem. This function takes an argument named s that's a reference to an ifstream. That is, the argument s is another name for the ifstream passed to us by the caller, not a copy of the caller's ifstream. As you probably have guessed, Susan had some questions about this whole concept.

Susan: How does Read make Shopinfo go get data?

Steve: Well, the argument s is a reference to the ifstream object provided by the caller; in this case, the ifstream object to which s refers is Shopinfo. That ifstream is connected to the file shop2.in.

Susan: How does Read do the reading? How come you are using >> without a cin statement?

Steve: cin isn't a statement, but a stream that is created automatically whenever we #include iostream.h in our source file. Therefore, we can read from it without connecting it to a file. In this case, we're reading from a different stream, namely s.

Susan: How come this is a void type? I would think it would return data being read from a file.

Steve: You would think so, wouldn't you? I love it when you're logical. However, what it actually does is to read data from a file into the object for which it was called. Therefore, it doesn't need a return value.

Susan: So the ifstream object is a transfer mechanism? That is, ifstream s; would read data from a file named s?

Steve: Yes, it's a transfer mechanism. However, ifstream s; would create an ifstream called s, that was not connected to any file; the file could be specified later. If we wanted to create an ifstream called s that was connected to a file called xyz, then we would write ifstream s("xyz");

Susan: OK. An ifstream just reads data from a file. It doesn't care which file, until you specify it?

Steve: Right.

Susan: What does this mean without cin? Is it just the same thing, only you can't call it cin because cin is for native use and this is a class? How come the >> is preceded by the argument s?

Steve: The s takes the place of cin, because we want to read from the stream s, not the stream cin.

Susan: Tell me what you mean by just a stream.

Steve: Think of it like a real stream, and the bytes are little barges that float downstream. Isn't that poetic? Anyway, there are three predefined streams that we get automatically when we #include <iostream.h>: cin, cout, and cerr. The first two we've already seen, and the last one is where error messages are sent.

There is one point that we haven't examined yet, though: how this routine determines that it's finished reading from the input file. With keyboard input, we process each line separately when it's typed in, but that won't do the job with a file, where we want to read all the items in until we get to the end of the file. We actually handle this detail in the main program itemtst2.cc, by asking ShopInfo whether there is any data left in the file; to be more precise, we call the ifstream member function fail() to ask the ShopInfo ifstream whether we have tried to read past the end of the file. If we have, then the result of that call to ShopInfo.fail() will be nonzero (which signifies true). If we haven't yet tried to read past the end of the file, then the result of that call will be 0 (which signifies false). How do we use this information?

We use it to decide whether to execute a break statement. This is a loop control device that interrupts processing of a loop whenever it is executed. The flow of control passes to the next statement after the end of the controlled block of the for statement.15 The loop will terminate in one of two ways. Either 100 records have been read, in which case i will be 100; or the end of the file is reached, in which case i is the number of records that have been read successfully.

Susan had some questions about the implementation of this program.

Susan: What is fail()?

Steve: It's a member function of the ifstream class.

Susan: Where did it come from?

Steve: From the library that defines that class.

Susan: But it could be used in other classes, right?

Steve: Not unless they define it as well.

Susan: How does all the data having been read translate into "nonzero"? What makes a "nonzero" value true?

Steve: That's a convention used by that function.

Susan: So anything other than 0 is considered true?

Steve: Yes.

Susan: Where did break come from?

Steve: It's another keyword like for; it means to terminate the loop that is in progress.

Susan: I do not understand what is actually happening with the program at this time. When is break implemented? Is it just to end the reading of the entire file?

Steve: We have to stop reading data when there is no more data in the file. The break statement allows us to terminate the loop when that occurs.

Susan: What do you mean that the loop will terminate either by 100 records being read or when the end of the file is reached? Isn't that the same thing?

Steve: It's the same thing only if there are exactly 100 records in the file.

Susan: So you mean when there are no more records to be read? So that the loop won't continue on till the end with nothing to do?

Steve: Exactly.

Susan: So does i just represent the number of records in the file?

Steve: Actually, it's the number of records that we've read.

Susan: Does this library have a card catalogue? I would like to know what else is in there.

Steve: There is a library reference manual for most libraries. If you get a library with a commercial compiler, that manual comes with the compiler documentation; otherwise, it's usually an on-line reference (that is, a help file).

Susan: A novice would not know this. Put it in the book.

Steve: Done.

Susan: Well, the program sounded like that indeed there were 100 records in the file. However, I see that in practice that might change, and why you would therefore need to have a break.

Steve: You obviously understand this.

Whether there are 100 records in the file or fewer than that number, obviously the number of items in the vector is equal to the current value of i. Or is it?

Don't Fence Me In

Let's examine this a bit more closely. It's actually quite easy to make a mistake in counting objects when writing a program. It's quite common to make the mistake of thinking you have one more or one less than the actual number of objects. In fact, this error is common enough to have a couple of widely known nicknames: an off by one error, also known as a fencepost error. The former name should be fairly evident, but the latter name may require some explanation. First, let's try it as a "word problem". If you have to put up a fence 100 feet long, and each section of the fence is 10 feet long, how many sections of fence do you need? Obviously, the answer is 10. Now how many fenceposts do you need? 11. The confusion caused by counting fenceposts when you should be counting segments of the fence (and vice-versa) is the cause of a fencepost error.

That's fine as a general rule, but what about this specific example? Well, let's start out by supposing that we have an empty file, so the sequence of events in the upper loop is as follows:

  1. Set i to 0.
  2. Is i less than 100? If not, exit. If so, continue.
  3. Use the Read function to try to read a record into the ith element of the AllItems vector.
  4. Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.
  5. If so, execute the break statement to exit the loop.

The answer to the question in step 4 is that in fact nothing was read, so we do execute the break and leave the loop. The value of i is clearly 0 here, because we never went back to the top of the loop; since we haven't read any records, setting InventoryCount to i works in this case.

Now let's try the same thing, but this time assuming that there is one record in the file. Here's the sequence of events:

  1. Set i to 0.
  2. Is i less than 100? If not, exit. If so, continue.
  3. Use the Read function to try to read a record into the ith element of the AllItems vector.
  4. Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.
  5. If so, execute the break statement to exit the loop. In this case, we haven't run off the end of the file, so we go back to the top of the loop, and continue as follows:
  6. Increment i to 1.
  7. Is i less than 100? If not, exit. If so, continue.
  8. Call Read to try to read a record into the AllItems vector.
  9. Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.
  10. If so, execute the break statement to exit the loop.

The second time through, we do execute the break. Since i is 1, and the number of elements read was also 1, it's correct to set the count of elements to i.

It should be pretty clear that this same logic applies to all the possible numbers of elements up to 99. But what if we have 100 elements in the file? Relax, I'm not going to go through these steps 100 times, but I think we should start out from the situation that would exist after reading 99 elements, and see if we get the right answer in this case too. After the 99th element has been read, i will be 99; we know this from our previous analysis that indicates that whenever we start executing the statements in the controlled block of the loop, i is always equal to the number of elements previously read. So here's the 100th iteration of the loop:

  1. Call Read to try to read a record into the AllItems array.
  2. Call ShopInfo.fail() to find out whether we've tried to read past the end of the file.
  3. If so, execute the break statement to exit the loop.
  4. Otherwise, increment i to 100.
  5. Is i less than 100? If not, exit. If so, continue.

Since i is not less than 100, we exit.

At this point, we've read 100 records and i is 100, so these two numbers are still the same. Therefore, we can conclude that setting InventoryCount equal to i when the loop is finished is correct; we have no fencepost error here.

Susan wasn't sure why I was hammering this fencepost thing into the ground:

Susan: Why are you always saying that "it's correct to set the count of elements to i"?

Steve: Because I'm showing how to tell whether or not we have a fencepost error. That requires a lot of analysis.

Actually, this whole procedure we've just been through reminds me of the professor who claimed that some point he was making was obvious. This was questioned by a student, so the professor spent 10 minutes absorbed in calculation and finally emerged triumphantly with the news that it was indeed obvious. Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this program. First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE itemtst2, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type itemtst2 to run the program. You'll see that it indeed prints out each StockItem object read from the file.

To run it under the debugger, make sure you are in the code subdirectory, and then type "RHIDE itemtst2". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger.

Can I Help You?

Of course, this isn't all we want to do with the items in the store's inventory. Since we have a working means of reading and displaying the items, let's see what else we might want to do with them. Here are a few possible transactions at the grocery store:

  1. George comes in and buys 3 bags of marshmallows. We have to adjust the inventory for the sale.
  2. Sam wants to know the price of a can of string beans.
  3. Judy comes in looking for chunky chicken soup; there's none on the shelf where it should be, so we have to check the inventory to see if we're supposed to have any.

All of these scenarios require the ability to find a StockItem object given some information about it. Let's start with the first example, which we might state as a programming task in the following manner: "Given the UPC from the bag of marshmallows, and the number of bags purchased, adjust the inventory by subtracting the number purchased from the previous quantity on hand." Figure itemtst3 is a program intended to solve this problem.

First attempt to update inventory of StockItems (code\itemtst3.cc) (Figure itemtst3)

code/itemtst3.cc

Here is a more detailed analysis of the steps that the program in Figure itemtst3 is intended to solve:

  1. Take the UPC from the item.
  2. For every item in the inventory list, check whether its UPC is the same as the one from the item.
  3. If it doesn't match, go back to step 2.
  4. If it does match, subtract the number purchased from the inventory.

There's nothing really new here except for the bool variable type, which we'll get to in a moment, and the -= operator that the program uses to adjust the inventory; -= is just like +=, except that it subtracts the right-hand value from the left-hand variable, while += adds.

The bool variable type is a new addition to C++. Expressions and variables of this type are limited to the two values true and false.16 This is a new data type that was added to C++ in the draft standard, and should be available on any compiler that conforms to the actual standard. We've been using the terms true and false to refer to the result of a logical expression such as if (x < y); similarly, a bool variable or function return value can be either true or false.

Access Denied

However, if you compile the program in Figure itemtst3, you'll find that it is not valid. The problem is the lines:

if (PurchaseUPC == AllItems[i].m_UPC) and

AllItems[i].m_InStock -= PurchaseCount;

The first of these lines could be translated into English as follows:

"if the input UPC is the same as the value of the m_UPC

member variable of the object stored in the ith element of the AllItems vector, then..."

while the second of these lines could be translated as:

"subtract the number of items purchased from the value of the m_InStock member variable of the object stored in the ith element of the AllItems vector".

While both of these lines are quite understandable to the compiler, they are also illegal, because they are trying to access private member variables of the StockItem class, namely m_UPC and m_InStock, from function main. Since main is not a member function of StockItem, this is not allowed. The error message from the compiler should look something like this:

itemtst3.cc: In function `int main()':

itemtst3.cc:34: member `m_UPC' is a private member of class `StockItem'

itemtst3.cc:43: member `m_InStock' is a private member of class `StockItem'

Does this mean that we can't accomplish our goal of updating the inventory? Not at all. It merely means that we have to do things "by the book" rather than going in directly and reading or changing member variables that belong to the StockItem class. Of course, we could theoretically "solve" this access problem by simply making these member variables public rather than private. However, this would allow anyone to mess around with the internal variables in our StockItem objects, which would defeat one of the main purposes of using class objects in the first place: that they behave like native types as far as their users are concerned. We want the users of this class to ignore the internal workings of its objects and merely use them according to their externally defined interface; the implementation of the class is our responsibility, not theirs.

This notion of implementation being separated from interface led to an excellent question from Susan:

Susan: Please explain to me why you needed to list those member variables as private in the interface of StockItem. Actually, why do they even need to be there at all? Well, I guess you are telling the compiler that whenever it sees the member variables that they will always have to be treated privately?

Steve: They have to be there so that the compiler can figure out how large an object of that class is. Many people, including myself, consider this a flaw in the language design, because private variables should really be private, not exposed to the class user's view.

Obviously, she'd lost her true novice status by this point. Six months after finding out what a compiler is, she was questioning the design decisions made by the inventor of C++; what is more, her objections were quite well founded.

As it happens, we can easily solve our access problem without exposing the implementation of our class to the user. All we have to do is to add a couple of new member functions called CheckUPC and DeductSaleFromInventory to the StockItem class; the first of these allows us to check whether a given UPC belongs to a given StockItem, and the second allows us to adjust the inventory level of an item.

Susan had another suggestion as to how to solve this problem, as well as a question about why I hadn't anticipated it in the first place:

Susan: Hey, wouldn't it be easier to write a special main that is a member function to get around this?

Steve: That's an interesting idea, but it wouldn't work. For one thing, main is never a member function; this is reasonable when you consider that you generally have quite a few classes in a program. Which one would main be a member function of?

Susan: So then all these new member functions do is to act as a gobetween linking the StockItem class and the inventory update program to compare data that is privately held in the StockItem class?

Steve: Yes, the new entries in the interface are designed to make the private data available in a safe manner. I think that's the same as what you're saying.

Susan: If you wanted to change the program, why didn't you just do it in the first place instead of breaking it down in parts like this?

Steve: Because that's not the way it actually happens in real life.

Susan: Do you think it less confusing to do that, and also does this act as an example of how you can modify a program as you see the need to do it?

Steve: Right on both counts.

Figure item4h shows the new, improved interface definition.

An enhanced interface for the StockItem class (code\item4.h) (Figure item4h)

code/item4.h

I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this part of the chapter; those files are item4.h, item4.cc, and itemtst4.cc, respectively. The declarations of the two new functions CheckUPC and DeductSaleFromInventory should be pretty easy to figure out: CheckUPC takes the UPC that we want to find and compares it to the UPC in its StockItem, then returns true if they match and false if they don't. Here's another good use for the bool data type; the only possible results of the CheckUPC function are that the UPC in the StockItem matches the one we've supplied (in which case we return true) or it doesn't match (in which case we return false). DeductSaleFromInventory takes the number of items sold and subtracts it from the previous inventory. But where did those other two member functions GetInventory and GetName come from?

The Customer Is Always Right

I added those functions because I noticed that the "itemtst" program wasn't very user-friendly. Originally it followed these steps:

  1. Ask for the UPC.
  2. Ask for the number of items purchased.
  3. Search through the list to see whether the UPC is legitimate.
  4. If so, adjust the inventory.
  5. If not, give an error message.
  6. Exit.

What's wrong with this picture? Well, for one thing, why should the program make me type in the number of items sold if the UPC is no good? Also, it never told me the new inventory or even what the name of the item was. It may have known these things, but it never bothered to inform me. So I changed the program to work as follows:

  1. Ask for the UPC.
  2. Search through the list to see whether the UPC was legitimate.
  3. If not, give an error message and exit.
  4. If the UPC was OK, then
    1. Display the name of the item and the number in stock.
    2. Ask for the number of items purchased.
    3. Adjust the inventory.
    4. Display a message with the name of the item and number of remaining units in inventory.
  5. Exit.

To do this, I needed those two new functions GetInventory and GetName, so as you've seen I added them to the class declaration. Figure item4new shows the implementation of all of these new functions.

Some new functions for the StockItem class (from code\item4.cc) (Figure item4new)

bool StockItem::CheckUPC(string ItemUPC)

{

if (m_UPC == ItemUPC)

return true;

return false;

}

void StockItem::DeductSaleFromInventory(short QuantitySold)

{

m_InStock -= QuantitySold;

}

short StockItem::GetInventory()

{

return m_InStock;

}

string StockItem::GetName()

{

return m_Name;

}

Our current itemtst example is getting to be enough like a real program that I'm going to start using the term application program (or equivalently, application) to refer to it sometimes. As is generally true of C++ programs, the responsibility for doing the user's work is divided up into a main program (or application program) and a set of classes (sometimes called infrastructure) used in the application. In this case, itemtst4.cc is the main program, or application program, whereas the other two files (item4.h and item4.cc) are the infrastructure. Figure itemtst4 shows the new, improved version of our application, which updates the inventory and actually tells the user what it's doing.

Updating StockItem inventory (code\itemtst4.cc) (Figure itemtst4)

#include <iostream.h>

#include <fstream.h>

#include "vector.h"

#include "string6.h"

#include "item4.h"

int main()

{

ifstream ShopInfo("shop2.in");

vector<StockItem> AllItems(100);

short i;

short InventoryCount;

short OldInventory;

short NewInventory;

string PurchaseUPC;

string ItemName;

short PurchaseCount;

bool Found;

for (i = 0; i < 100; i ++)

{

AllItems[i].Read(ShopInfo);

if (ShopInfo.fail() != 0)

break;

}

InventoryCount = i;

cout << "What is the UPC of the item? ";

cin >> PurchaseUPC;

Found = false;

for (i = 0; i < InventoryCount; i ++)

{

if (AllItems[i].CheckUPC(PurchaseUPC) == true)

{

Found = true;

break;

}

}

if (Found == true)

{

OldInventory = AllItems[i].GetInventory();

ItemName = AllItems[i].GetName();

cout << "There are currently " << OldInventory << " units of "

<< ItemName << " in stock." << endl;

cout << "How many items were sold? ";

cin >> PurchaseCount;

AllItems[i].DeductSaleFromInventory(PurchaseCount);

cout << "The inventory has been updated." << endl;

NewInventory = AllItems[i].GetInventory();

cout << "There are now " << NewInventory << " units of "

<< ItemName << " in stock." << endl;

}

else

cout << "Can't find that item. Please check UPC" << endl;

return 0;

}

This code should be pretty easy to follow; it simply implements the first item purchase scenario I outlined in the list on page . Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this program. First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE itemtst4, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type itemtst4 to run the program.

To run it under the debugger, make sure you are in the code subdirectory, and then type "RHIDE itemtst4". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger.

In either case, the program will start up and ask you for the UPC; you can use 7904886261, which is the (made-up) UPC for "antihistamines". Type in that number and hit ENTER.17

Next Customer, Please?

Now let's consider what might be needed to handle some of the other possibilities, starting with the second scenario in that same list. To refresh your memory, here it is again: "Sam wants to know the price of a can of string beans".

A possible way to express this as a programming task is "Given a UPC, look up the price of the item in the inventory".

Here is a set of steps to solve this problem:

  1. Ask for the UPC.
  2. Search through the list to see whether the UPC is legitimate.
  3. If not, give an error message and exit.
  4. If the UPC is OK, then display the name and price of the item.
  5. Exit.

Have you noticed that this solution is very similar to the solution to the first problem? For example, the search for an item with a given UPC is exactly the same. It seems wasteful to duplicate code rather than using the same code again, and in fact we've seen how to avoid code duplication by using a function. Now that we're doing "object-oriented" programming, perhaps this new function should be a member function instead of a global one.

This is a good idea, except that the search function can't be a member function of StockItem, because we don't have the right StockItem yet; if we did, we wouldn't need to search for it. Therefore, we have to create a new class that contains a member variable that is a vector of StockItems and write the search routine as a member function of this new class; the new member function would look through its vector to find the StockItem we want. Then we can use the member functions of StockItem to do the rest of the work. Figure invent1 shows the interface (class declaration) for this new class, called Inventory.

Interface of Inventory class (code\invent1.h) (Figure invent1)

code/invent1.h

I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this chapter; those files are invent1.h, invent1.cc, and itemtst5.cc, respectively.

Susan was somewhat surprised that I would even consider writing a global function to find a StockItem:

Susan: What do you mean by making this a member function instead of a global function? When was it ever a global function?

Steve: It wasn't any kind of function before; we were just duplicating code. However, making it a function would make sense; the question is what kind of function, global or member?

Susan: I am not sure if I truly understand the problem as to why you can't search StockItem as a member function.

Steve: A member function of StockItem always accesses a particular StockItem. However, our problem is that we don't know which StockItem we want; therefore, a member function, which necessarily applies to a particular StockItem, won't solve our problem.

Susan: OK, Stevie, here is the deal. Why would you even consider making this a global function? Of course it is a member function. We are doing object oriented programming aren't we?

Steve: My, aren't we knowledgeable all of a sudden? Who was that person who was completely ignorant of programming eight months ago?

Susan: You've got me there. But seriously, what would be the advantage of making it a global function rather than a member function? This is what has me bothered about the whole thing.

Steve: There wouldn't be any advantage. I just wanted to point out that it clearly can't be a member function of StockItem, and indicate the possible alternatives.

Susan: Oh, then so far that is all our program is able to do? It is unable to locate one item of all possible items and display it just from the UPC code? In fact that is what we are trying to accomplish, right?

Steve: Exactly.

Susan also wasn't sure why we needed LoadInventory.

Susan: What does the code short LoadInventory (ifstream& InputStream); do? Does it just give you an object named LoadInventory that reads a file that has a reference argument named InputStream? I don't get this.

Steve: That's quite close. The line you're referring to is the declaration of a function named LoadInventory, which takes a reference to an ifstream. The implementation of the function, as you'll see shortly, reads StockItem records from the file connected to the ifstream.

Once that was cleared up, she had some questions about the way the FindItem function works, including its interface.

Susan: Is the argument UPC to the FindItem function a string because it is returning the name of a stock item?

Steve: That argument is the input to the FindItem function, not its output; therefore, it's not "returning" anything. FindItem returns the StockItem that it finds. Or did I misunderstand your question?

Susan: Let's see if I even know what I was asking here. OK, how about this: I wanted to know why UPC was a string and not a short, since a UPC is usually a number. In this case, it will be returning a name of a "found item" so that is why it is a string, right?

Steve: No, it's because the UPC won't fit in any of the types of numbers we have available. Thus, the most sensible way to store it is as a string. Since we don't use it in calculations anyway, that's not much of a restriction.

Susan: Oh. OK. So a string is more useful for storing numbers that are somewhat lengthy as long as you don't calculate with those numbers. They are nothing more than "numerical words"?

Steve: Exactly.

Most of this should be fairly self-explanatory by this point. We start out with the default constructor which makes an empty Inventory.18 Figure inventorydefaultcons has the implementation for the default constructor.

Default constructor for Inventory class (from code\invent1.cc) (Figure inventorydefaultcons)

Inventory::Inventory()

: m_Stock (vector<StockItem>(100)),

m_StockCount(0)

{

}

There's nothing complex here; we're using the member initialization list to initialize the m_Stock variable to a newly constructed vector of 100 StockItems and the number of active StockItems to 0. The latter, of course, is because we haven't yet read any data in from the file.

Then we have a couple of handy functions. The first is LoadInventory, which will take data from an ifstream and store it in its Inventory object, just as we did with the AllItems vector in our application itemtst4.cc.

Susan had a question about this:

Susan: How did you know that you were going to need to use an ifstream again?

Steve: Because we're reading data from a file into a vector of StockItems, and reading data from a file is what ifstreams are for.

Figure inventoryload shows the implementation of LoadInventory.

LoadInventory function for Inventory class (from code\invent1.cc) (Figure inventoryload)

short Inventory::LoadInventory(ifstream& InputStream)

{

short i;

for (i = 0; i < 100; i ++)

{

m_Stock[i].Read(InputStream);

if (InputStream.fail() != 0)

break;

}

m_StockCount = i;

return m_StockCount;

}

Now we come to the FindItem member function. Its declaration is pretty simple: It takes an argument of type string which contains the UPC that we're looking for. Its implementation is pretty simple, too: It will search the Inventory object for the StockItem that has that UPC and return a copy of that StockItem, which can then be interrogated to find the price or whatever other information we need.

However, there's a serious design issue here: What should this function return if the UPC doesn't match the UPC in any of the StockItem entries in the Inventory object? The application program has to be able to determine whether or not the UPC is found. In the original program this was no problem, because the main program maintained that information itself. But in this case, the member function FindItem has to communicate success or failure to the caller somehow.

Of course, we could use a return value of true or false to indicate whether the UPC is found, but we're already using the return value to return the StockItem to the calling function. We could add a reference argument to the FindItem function and use it to set the value of a variable in the caller's code, but that's very nonintuitive; functions that use arguments only for input are easier to use and less likely to cause surprises.

Nothing Ventured, Nothing Gained

There's one more possibility. We can return a null object of the StockItem class; that is, an object that exists solely to serve as a placeholder, representing the desired object that we couldn't find.

I like this solution, because when the member function terminates, the application program has to test something anyway to see if the desired StockItem was found; why not test whether the returned object is a null StockItem? This solution, while quite simple, requires a minor change to our implementation of StockItem: We have to add an IsNull member function to our StockItem class so that we can tell whether the returned StockItem is a null StockItem or a "normal" one. We have to add the line bool IsNull(); to the class interface and provide the implementation as shown in Figure isnull. I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this part of the chapter; those files are item5.h, item5.cc, and itemtst5.cc, respectively.

The implementation of IsNull (from code\item5.cc) (Figure isnull)

bool StockItem::IsNull()

{

if (m_UPC == "")

return true;

return false;

}

As you can see, not much rocket science is involved in this member function: all we do is check whether the UPC is the null string "". If it is, we return true; otherwise, we return false. Since no real item can have a UPC of "", this should work well. Let's hear from Susan on the topic of this function (and function return values in general).

Susan: This is something I have not thought about before: When you call a function where does the return value go?

Steve: Wherever you put it. If you say x = sum(weight);, then the return value goes into x. If you just say sum(weight);, then it is discarded.

Susan: Why is it discarded?

Steve: Because you didn't use it; therefore, the compiler assumes you have no further use for it.

Susan: So the return value can be used in only one place?

Steve: Yes, unless you save it in a variable, in which case you can use it however you like.

Figure inventoryfinditem shows the implementation of FindItem, which uses CheckUPC to check whether the requested UPC is the one in the current item and returns a null StockItem if the desired UPC isn't found in the inventory list.

FindItem function for Inventory class (from code\invent1.cc) (Figure inventoryfinditem)

StockItem Inventory::FindItem(string UPC)

{

short i;

bool Found = false;

for (i = 0; i < m_StockCount; i ++)

{

if (m_Stock[i].CheckUPC(UPC) == true)

{

Found = true;

break;

}

}

if (Found == true)

return m_Stock[i];

return StockItem();

}

Here's my interchange with Susan on how we're using CheckUPC:

Susan: About the first if statement, if (m_Stock[i].CheckUPC(UPC) == true): does that mean if you find the UPC you are looking for then the program breaks and you don't need to continue looking? In that case, what does the statement Found = true; do? It looks as if you are setting Found to the value true.

Steve: That's right. If we've actually found the item we're looking for, then Found will have been set to true, so we'll return the real item; otherwise, we'll return a null StockItem to indicate that we couldn't find the one requested.

After we get a copy of the correct StockItem and update its inventory via DeductSaleFromInventory, we're not quite done; we still have to update the "real" StockItem in the Inventory object. This is the task of the last function in our Inventory class: UpdateItem. Figure inventoryupdateitem shows its implementation.

UpdateItem function for Inventory class (from code\invent1.cc) (Figure inventoryupdateitem)

bool Inventory::UpdateItem(StockItem Item)

{

string UPC = Item.GetUPC();

short i;

bool Found = false;

for (i = 0; i < m_StockCount; i ++)

{

if (m_Stock[i].CheckUPC(UPC) == true)

{

Found = true;

break;

}

}

if (Found == true)

m_Stock[i] = Item;

return Found;

}

Why do we need this function? Because we are no longer operating on the "real" StockItem, as we had been when we accessed the inventory vector directly in the previous version of the application program. Instead, we are getting a copy of the StockItem from the Inventory object and changing that copy; thus, to have the final result put back into the Inventory object, we need to use the UpdateItem member function of Inventory, which overwrites the original StockItem with our changed version.

This function needs another function in the StockItem class to get the UPC from a StockItem object, so that UpdateItem can tell which object in the m_Stock vector is the one that needs to be updated. This additional function, GetUPC, is shown in Figure getupcimp.

The implementation of GetUPC (from code\item5.cc) (Figure getupcimp)

string StockItem::GetUPC()

{

return m_UPC;

}

The application program also needs one more function, GetPrice(), to be added to the interface of StockItem, to retrieve the price from the object once we have found it. This is shown in Figure getpriceimp.

The implementation of GetPrice (from code\item5.cc) (Figure getpriceimp)

short StockItem::GetPrice()

{

return m_Price;

}

We're almost ready to examine the revised test program. First, though, let's pause for another look at all of the interfaces and implementations of the StockItem and Inventory classes. The interface for the Inventory class is in Figure invent1.h.

Current interface for Inventory class (code\invent1.h) (Figure invent1.h)

code/invent1.h

Figure invent1.cc contains the implementation for Inventory.

Current implementation for Inventory class (code\invent1.cc) (Figure invent1.cc)

#include <iostream.h>

#include <fstream.h>

#include "vector.h"

#include "string6.h"

#include "item5.h"

#include "invent1.h"

Inventory::Inventory()

: m_Stock (vector<StockItem>(100)),

m_StockCount(0)

{

}

short Inventory::LoadInventory(ifstream& InputStream)

{

short i;

for (i = 0; i < 100; i ++)

{

m_Stock[i].Read(InputStream);

if (InputStream.fail() != 0)

break;

}

m_StockCount = i;

return m_StockCount;

}

StockItem Inventory::FindItem(string UPC)

{

short i;

bool Found = false;

for (i = 0; i < m_StockCount; i ++)

{

if (m_Stock[i].CheckUPC(UPC) == true)

{

Found = true;

break;

}

}

if (Found == true)

return m_Stock[i];

return StockItem();

}

bool Inventory::UpdateItem(StockItem Item)

{

string UPC = Item.GetUPC();

short i;

bool Found = false;

for (i = 0; i < m_StockCount; i ++)

{

if (m_Stock[i].CheckUPC(UPC) == true)

{

Found = true;

break;

}

}

if (Found == true)

m_Stock[i] = Item;

return Found;

}

Figure item5.h shows the interface for StockItem.

Current interface for StockItem class (code\item5.h) (Figure item5.h)

code/item5.h

The implementation for StockItem is in Figure item5.cc.

Current implementation for StockItem class (code\item5.cc) (Figure item5.cc)

#include <iostream.h>

#include <fstream.h>

#include "string6.h"

#include "item5.h"

StockItem::StockItem()

: m_InStock(0), m_Price(0), m_Name(),

m_Distributor(), m_UPC()

{

}

StockItem::StockItem(string Name, short InStock,

short Price, string Distributor, string UPC)

: m_InStock(InStock), m_Price(Price), m_Name(Name),

m_Distributor(Distributor), m_UPC(UPC)

{

}

void StockItem::Display()

{

cout << "Name: ";

cout << m_Name << endl;

cout << "Number in stock: ";

cout << m_InStock << endl;

cout << "Price: ";

cout << m_Price << endl;

cout << "Distributor: ";

cout << m_Distributor << endl;

cout << "UPC: ";

cout << m_UPC << endl;

cout << endl;

}

void StockItem::Read(ifstream& s)

{

s >> m_Name;

s >> m_InStock;

s >> m_Price;

s >> m_Distributor;

s >> m_UPC;

}

bool StockItem::CheckUPC(string ItemUPC)

{

if (m_UPC == ItemUPC)

return true;

return false;

}

void StockItem::DeductSaleFromInventory(short QuantitySold)

{

m_InStock -= QuantitySold;

}

short StockItem::GetInventory()

{

return m_InStock;

}

string StockItem::GetName()

{

return m_Name;

}

bool StockItem::IsNull()

{

if (m_UPC == "")

return true;

return false;

}

short StockItem::GetPrice()

{

return m_Price;

}

string StockItem::GetUPC()

{

return m_UPC;

}

To finish this stage of the inventory control project, Figure itemtst5 is the revised test program that uses the Inventory class rather than doing its own search through a vector of StockItems. This program can perform either of two operations, depending on what the user requests. Once the UPC has been typed in, the user is prompted to type either "C" for price check or "S" for sale. Then an if statement selects which of the two operations to perform. The code for the S (i.e., sale) operation is the same as it was in the previous version of this application, except that, of course, at that time it was the only possible operation so it wasn't controlled by an if statement. The code for the C (i.e., price check) operation is new, but it's very simple. It merely displays both the item name and the price.

Updated inventory application (code\itemtst5.cc) (Figure itemtst5)

#include <iostream.h>

#include <fstream.h>

#include "string6.h"

#include "item5.h"

#include "invent1.h"

int main()

{

ifstream InputStream("shop2.in");

string PurchaseUPC;

short PurchaseCount;

string ItemName;

short OldInventory;

short NewInventory;

Inventory MyInventory;

StockItem FoundItem;

string TransactionCode;

MyInventory.LoadInventory(InputStream);

cout << "What is the UPC of the item? ";

cin >> PurchaseUPC;

FoundItem = MyInventory.FindItem(PurchaseUPC);

if (FoundItem.IsNull() == true)

{

cout << "Can't find that item. Please check UPC." << endl;

return 0;

}

OldInventory = FoundItem.GetInventory();

ItemName = FoundItem.GetName();

cout << "There are currently " << OldInventory << " units of "

<< ItemName << " in stock." << endl;

cout << "Please enter transaction code as follows:\n";

cout << "S (sale), C (price check): ";

cin >> TransactionCode;

if (TransactionCode == "C" || TransactionCode == "c")

{

cout << "The name of that item is: " << ItemName << endl;

cout << "Its price is: " << FoundItem.GetPrice();

}

else if (TransactionCode == "S" || TransactionCode == "s")

{

cout << "How many items were sold? ";

cin >> PurchaseCount;

FoundItem.DeductSaleFromInventory(PurchaseCount);

MyInventory.UpdateItem(FoundItem);

cout << "The inventory has been updated." << endl;

FoundItem = MyInventory.FindItem(PurchaseUPC);

NewInventory = FoundItem.GetInventory();

cout << "There are now " << NewInventory << " units of "

<< ItemName << " in stock." << endl;

}

return 0;

}

The only part of the program that might not be obvious at this point is the expression in the if statement that determines whether the user wants to enter a price check or sale transaction. The first part of the test is if (TransactionCode == "C" || TransactionCode == "c"). The || is the "logical or" operator. An approximate translation of this expression is "if at least one of the two expressions on its right or left is true, then produce the result true; if they're both false, then produce the result false".19 In this case, this means that the if statement will be true if the TransactionCode variable is either C or c. Why do we have to check for either a lower- or upper-case letter, when the instructions to the user clearly state that the choices are C or S?

This is good practice because users generally consider upper and lower case letters to be equivalent. Of course, as programmers, we know that the characters c and C are completely different; however, we should humor the users in this harmless delusion. After all, they're our customers!

Susan had a couple of questions about this program.

Susan: What do the following output statements mean: cout << S (sale); and cout << C (price check);? I am not clear as to what they are doing.

Steve: Nothing special; the prompts S (sale) and C (price check) are just to notify the user what his or her choices are.

Susan: OK, so the line with the || is how you tell the computer to recognize upper case as well as lower case to have the same meaning?

Steve: Yes, that's what we're doing here.

Susan: So what do you call those || thingys?

Steve: They're called "vertical bars". The operator that is spelled || is called a "logical OR" operator, because it results in the value true if either the left-hand or the right-hand expression is true (or if both are true).

Susan: What do you mean by using else and if in the line else if (TransactionCode == "S" || TransactionCode == "s")? I don't believe I have seen them used together before.

Steve: I think you're right. Actually, it's not that mysterious: As always, the else means that we're specifying actions to be taken if the original if isn't true. The second if merely checks whether another condition is true and executes its controlled block if that is the case.

Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out this program. First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE itemtst5, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type itemtst5 to run the program. When the program asks for a UPC, you can use 7904886261, which is the (made-up) UPC for "antihistamines". When the program asks you for a transaction code, type S for "sale" or P for "price check", and then hit ENTER.

To run it under the debugger, make sure you are in the code subdirectory, and then type "RHIDE itemtst5". Again, do *not* add the ".cc" to the end of the file name. Once RHIDE has started up, you can step through the program by hitting F8, which will treat any function call as one statement, or by hitting F7, which will step into any function call. Any time you want to see the display that the user would see when running the program normally, hit Alt-F5, then ENTER to get back to the debugger.

Paging Rosie Scenario

By this point, you very understandably might have gotten the notion that we have to make changes to our classes every time we need to do anything slightly different in our application program. In that case, where's the advantage of using classes instead of just writing the whole program in terms of shorts, chars, and so on?

Well, this is your lucky day. It just so happens that the next (and last) scenario we are going to examine requires no more member functions at all; in fact, we don't even have to change the application program. Here it is, for reference: "Judy comes in looking for chunky chicken soup; there's none on the shelf where it should be, so we have to check the inventory to see if we're supposed to have any".

The reason we don't have to do anything special for this scenario is that we're already displaying the name and inventory for the item as soon as we find it. Of course, if we hadn't already handled this issue, there are many other ways that we could solve this same problem. For example, we could use the Display member function of StockItem to display an item as soon as the UPC lookup succeeds, rather than waiting for the user to indicate what operation our application is supposed to perform.

For that matter, we'd have to consider a number of other factors in writing a real application program, even one that does such a simple task as this one. For example, what would happen if the user indicated that 200 units of a particular item had been sold when only 100 were in stock? Also, how would we find an item if the UPC isn't available? The item might very well be in inventory somewhere, but the current implementation of Inventory doesn't allow for the possibility of looking up an item by any information other than the UPC.

Although these topics and many others are essential to the work of a professional programmer, they would take us too far afield from our purpose in this book, which is to teach you how to program using C++. Therefore, we will leave them for another day (and another book). Now let's review what we've covered in this chapter.

Review

The most important concept in this chapter is the idea of creating user defined data types. In C++, this is done by defining a class for each such data type. Each class has both a class interface, which describes the behavior that the class displays to the "outside world" (i.e., other, unrelated functions), and a class implementation, which tells the compiler how to perform the behaviors promised in the interface definition. A variable of a class type is called an object. With proper attention to the interface and the details of implementation, it is possible to make objects behave just like native variables; that is, they can be initialized, assigned, compared, passed as function arguments, and returned as function return values.

Both the interface and the implementation of a class are described in terms of the functions and variables of which the class is composed; these are called member functions and member variables, because they belong to the class rather than being "free-floating" or localized to one function like the global functions and local variables we encountered earlier.

Of course, one obvious question is why we need to make up our own variable types. What's wrong with char, short, and the rest of the native types built into C++? The answer is that it's easier to write an inventory control program, for example, if we have data types representing items in the stock of a store, rather than having to express everything in terms of the native types. An analogy is the universal preference of professionals to use technical jargon rather than "plain English": Jargon conveys more information, more precisely, in less time.

The idea of using objects rather than functions as the fundamental building blocks of programming is the basis of the "object-oriented programming" paradigm.20 Then we examined how creating classes differs from using classes, as we have been doing throughout the book. A fairly good analogy is that creating your own classes is to using classes as writing a program is to using a program.

Next, we went through the steps needed to actually create a new class; our example is the StockItem class, which is designed to allow tracking of inventory for a small grocery store. These steps include writing the interface definition, writing the implementation, writing the program that uses the class, compiling the implementation, compiling the program that uses the class, and linking the object files resulting from these compilation steps together with the standard libraries to produce the final executable program.

Then we moved from the general to the specific, analyzing the particular data and functions that the StockItem class needed to perform its duties in an application program. The member variables needed for each StockItem object included the name, count, distributor, price, and UPC. Of course, merely having these member variables doesn't make a StockItem object very useful, if it can't do anything with them. This led us to the topic of what member functions might be needed for such a class.

Rather than proceed immediately with the specialized member functions that pertain only to StockItem, however, we started by discussing the member functions that nearly every class needs to make its objects act like native variables. A class that has (at least) the capabilities of a native type is called a concrete data type. Such a class requires the following member functions:

  1. A default constructor, which makes it possible to create an object of that class without supplying any initial data,
  2. A copy constructor, which makes it possible to create a new object of this type with the same contents as an existing object of the same type,
  3. An assignment operator, which copies the contents of one object of this type to another object of the same type,
  4. A destructor, which performs whatever cleanup is needed when an object of this type "dies".

Since these member functions are so important to the proper functioning of a class, the compiler will create a version of each of them for us if we don't write them ourselves. In the case of StockItem, these compiler-generated member functions are perfectly acceptable, with the exception of the default constructor. The compiler-generated default constructor doesn't initialize a new StockItem object to a valid state, so we had to write that constructor ourselves to be sure of what a newly created StockItem contains. Next, we looked at the first version of a class interface specification for StockItem (Figure item1h), which tells the user (and the compiler) exactly what functions objects of this class can perform. Some items of note in this construct are these:

  1. The access specifiers public and private, which control access to the implementation of a class by functions not in the class (nonmember functions). Member variables and functions in the public section are available for use by nonmember functions, whereas member variables and functions in the private section are usable only by member functions.
  2. The declarations of the constructor functions, which construct a new object of the class. The first noteworthy point about constructors is that they have the same name as the class, which is how the compiler identifies them as constructors. The second point of note is that there can be more than one constructor for a given class; all constructors have the same name, and are distinguished by their argument lists. This facility, called function overloading, is applicable to C++ functions in general, not just constructors. That is, you can have any number of functions with the same name as long as they have different argument lists; the difference in argument lists is enough to make the compiler treat them as different functions. In this case, we have written two constructors: the default constructor, which is used to create a StockItem when we don't specify an initial value, and a constructor that has arguments to specify values for all of the member variables.21
  3. The declaration of a "normal" member function (that is, not a constructor or other predefined function) named Display, which as its name indicates, is used to display a StockItem on the screen.
  4. The declaration of the member variables of StockItem, which are used to keep track of the information for a given object of the StockItem class.

Once we'd defined the class interface, we started on the class implementation by writing the default constructor for the StockItem class: StockItem::StockItem(). The reason for the doubled name is that when we write the implementation of a member function, we have to specify what class that member function belongs to; in this example, the first StockItem is the name of the class, whereas the second StockItem is the name of the function, which, as always with constructors, has the same name as the class. By contrast, we didn't have to specify the class name when declaring member functions in the interface definition, because all functions defined there are automatically member functions of that class. During this discussion, we saw that the preferred way to set the values of member variables is by using a member initialization list.

The next topic we visited was the scope of member variables, which is class scope. Each object of a given class has one set of member variables, which live as long as the object does. These member variables can be accessed from any member function as though they were global variables.

Then we examined how the default constructor was actually used in the example program, discovering that the line StockItem soup; was enough to cause it to be called. This is appropriate because one of the design goals of C++ was to allow a class object to be as easy to use as a native variable. Since a native variable can be created simply by specifying its type and name, the same should be true of a class object.

This led to a discussion of the fact that the person who writes a class isn't always the person who uses it. One reason for this is that the skills required to write a program using a class are not the same as those required to create the class in the first place.

Next, we covered the other constructor for the StockItem class. This one has arguments specifying the values for all of the member variables that make up the data part of the class.

Then we got to the final function of the first version of the StockItem class: the Display function, which as its name indicates is used to display the contents of a StockItem on the screen. This function uses the pre-existing ability of << to display the shorts and strings that hold the contents of the StockItem. The return type of this function is a type we hadn't seen before, void, which simply means that there is no return value from this function. We don't need a return value from the Display function because we call it solely for its side effect: displaying the value of its StockItem on the screen.

Next, we took up the private part of the StockItem class definition, which contains the member variables. We covered two reasons why it is a good idea to keep the member variables private: First, it makes debugging easier, because only the member functions can modify the member variables; second, we can change the names or types of our member variables or delete them from the class definition much more easily if we don't have to worry about what other functions might be relying on them. While we were on the subject of the member variables of StockItem, I also explained how we could use a short to store a price: By expressing the price in cents, rather than dollars and cents, any price up to $327.67 could be stored in such a variable.

As we continued with the analysis of how the StockItem objects would be used, we discovered that our example program actually needed a vector of such objects, one for each different item in the stock. We also needed some way to read the information for these StockItem objects from a disk file, so we wouldn't have to type it in every time we started the program up. So the next program we examined provided this function via a C++ library class we hadn't seen before: ifstream (for input from a file). We also added a new function called Read to use this new class to read information for a StockItem from the file containing that information.

While looking at the implementation of the new Read member function, we ran into the idea of a reference argument, which is an argument that is another name for the caller's variable, rather than a copy of that variable (a value argument). This makes it possible to change the caller's variable by changing the value of the argument in the function. In most cases, we don't want to be able to change the caller's variable, but it is essential when reading from a stream, because otherwise we'd get the same data every time we read something from the stream. Therefore, we have to use a reference argument in this case, so that the stream's internal state will be updated correctly when we retrieve data from it.

Then we got to the question of how we could tell when there were no data left in the input file; the answer was to call the ifstream member function fail, which returns zero if some data remain in the stream, and nonzero if we have tried to read past the end of the file. We used a nonzero return value from fail to trigger a break statement, which terminates whatever loop contains the break. In this case, the loop was the one that read data from the input file, so the loop would stop whenever we got to the end of the input file or when we had read 100 records, whichever came first.

This led to a detailed investigation of whether the number of records read was always calculated correctly. The problem under discussion was the potential for a fencepost error, also known as an off by one error. After careful consideration, I concluded that the code as written was correct.

Having cleared up that question, we proceeded to some other scenarios that might occur in the grocery store for which this program theoretically was being written. All of the scenarios we looked at had a common requirement: to be able to look up a StockItem, given some information about it. We first tried to handle this requirement by reading the UPC directly from each StockItem object in the vector. When we found the correct StockItem, we would display and update the inventory for that StockItem. However, this didn't compile, because we were trying to access private member variables of a StockItem object from a nonmember function, which is illegal. While we could have changed those variables from private to public, that would directly contradict the reason that we made them private in the first place; that is, to prevent external functions from interfering in the inner workings of our StockItem objects. Therefore, we solved the problem by adding some new member functions (CheckUPC and DeductSaleFromInventory) to check the UPC of a StockItem and manipulate the inventory information for each StockItem, respectively. At the same time, we examined a new data type, bool, which is limited to the two values true and false; it is handy for keeping track of information such as whether we have found the StockItem object we are looking for, and communicating such information back to a calling function.

While I was making these changes, I noticed that the original version of the test program wasn't very helpful to its user; it didn't tell the user whether the UPC was any good, the name of the item, or how much inventory was available for sale. So I added some more member functions (GetInventory and GetName) to allow this more "user-friendly" information to be displayed.

Then we progressed to the second of the grocery store scenarios, in which the task was to find the price of an item, given its UPC. This turned out to be very similar to the previous problem of finding an item to update its inventory. Therefore, it was a pretty obvious step to try to make a function out of the "find an item by UPC" operation, rather than writing the code for the search over again. Since we're doing "object-oriented" programming, such a function should probably be a member function. The question was "of which class?" It couldn't be a member function of StockItem, because the whole idea of this function was to locate a StockItem. A member function of StockItem needs a StockItem object to work on, but we didn't have the StockItem object yet.

The solution was to make another class, called Inventory, which had member functions to load the inventory information in from the disk file (LoadInventory) and search it for a particular StockItem (FindItem). Most of this class was pretty simple, but we did run into an interesting design question: What should the FindItem function return if the UPC didn't match anything in the inventory? After some consideration, I decided to use a null object of the class StockItem; that is, one that exists solely to serve as a placeholder representing a non-existent object. This solution required adding an IsNull member function to the StockItem class, so that the user of FindItem could determine whether the returned object was "real" or just an indication of an incorrect UPC.

Then we updated the test program to use this new means of locating a StockItem. Since the new version of the test program could perform either of two functions (price check or sale), we also added some output and input statements to ask the user what he wanted to do. To make this process more flexible, we allowed the user to type in either an upper or lower case letter to select which function to perform. This brought up the use of the "logical OR" operator || to allow the controlled block of an if statement to be executed if either (or both) of two expressions is true. We also saw how to combine an else with a following if statement, when we wanted to select among more than two alternatives.

We needed two more functions to make this new version of the application program work correctly: one to update the item in the inventory (Inventory::UpdateItem(StockItem item)) and one to get the UPC of a StockItem (StockItem::GetUPC()). The reason that we had to add these new functions to the interfaces of Inventory and StockItem, respectively, is that we were no longer operating on the "real" StockItem, as we had been when we accessed the inventory vector directly in the previous version of the application program. Instead, we were getting a copy of the StockItem from the Inventory object and changing that copy; thus, to have the final result put back into the Inventory object, we had to add the UpdateItem member function of Inventory, which overwrote the original StockItem with our changed version. The GetUPC function's role in all this was to allow the UpdateItem function to look up the correct StockItem to be replaced without the main program having to pass the UPC in explicitly; instead, the GetUPC function allowed the UpdateItem function to retrieve the correct UPC from the updated object provided by the main program.

This brought us to the final scenario, which required us to look up the inventory for an item, given its UPC. As it happened, we had already solved that problem by the simple expedient of displaying the name and inventory of the StockItem as soon as it was located.

Finally, I mentioned a few other factors, such as alternative means of looking up an item without knowing its UPC, that would be important in writing a real application program and noted that we couldn't go into them here due to space limitations.

Exercises

1. In a real inventory control program, we would need to do more than merely read the inventory information in from a disk file, as we have done in this chapter. We'd also want to be able to write the updated inventory back to the disk file via an ofstream object, which is exactly like an ifstream object except that it allows us to write to a file rather than reading from one. Modify the header files item5.h and invent1.h to include the declarations of the new functions StockItem::Write and Inventory::StoreInventory, needed to support this new ability.

2. Implement the new functions that you declared in exercise 1. Then update the test program to write the changed inventory to a new file. To connect an ofstream called OutputStream to a file named "test.out", you could use the line:

ofstream OutputStream("test.out");.

Conclusion

In this chapter, we've delved into the concepts and implementations of classes and objects, which are the constructs that make C++ an object-oriented language. Of course, we have only scratched the surface of these powerful topics; in fact, we'll spend the rest of this book on the fundamentals of classes and objects. Unfortunately, it's impossible to cover these constructs and all of their uses in any one book, no matter how long or detailed it may be, and I'm not going to try to do that. Instead, we'll continue with our in-depth examination of the basics of object-oriented programming. In the next chapter, we'll start on the task of creating a string class like the one that we've been using so far in this book.

Answers to Exercises

1. Here is the new function declaration that needs to be added to the StockItem interface definition (from code\item6.h):

void Write(ofstream& s);

and the one to be added to the Inventory interface definition, (from code\invent2.h):

void StoreInventory(ofstream& OutputStream);

2. Figure writemember shows the implementation of the Write member function for StockItem, and Figure inventex2ans is the implementation of the StoreInventory member function of the Inventory class. As you can see, neither of these functions is tremendously complex or, for that matter, very different from its counterpart used to read the data in from the file in the first place.

The Write member function for the StockItem class (from code\item6.cc) (Figure writemember)

void StockItem::Write(ofstream& s)

{

s << m_Name << endl;

s << m_InStock << endl;

s << m_Price << endl;

s << m_Distributor << endl;

s << m_UPC << endl;

}

The StoreInventory member function for the Inventory class (from code\invent2.cc) (Figure inventex2ans)

void Inventory::StoreInventory(ofstream& OutputStream)

{

short i;

for (i = 0; i < m_StockCount; i ++)

m_Stock[i].Write(OutputStream);

return;

}

Finally, Figure itemtst6chg shows the changes needed to the application program to write the updated inventory back to a new file.

The changes to the application program (from code\itemtst6.cc) (Figure itemtst6chg)

	ofstream OutputStream("shop2.out");

MyInventory.StoreInventory(OutputStream);

Of course, in a real program, it would probably be better to write the updated inventory back to the original file, so that the next time we ran the program, the updated inventory would be used. However, in the case of a test application, it's simpler to avoid modifying the input file so we can run the same test again if necessary.

Assuming that you've installed the software from the CD-ROM in the back of this book, you can try out my solution to these exercises. First, you have to compile it by changing to the code subdirectory under the main directory where you installed the software, and typing RHIDE itemtst6, then using the "Make" command from the "Compile" menu. Then exit back to DOS and type itemtst6 to run the program. When the program asks for a UPC, you can use 7904886261, which is the (made-up) UPC for "antihistamines".

Footnotes

  1. In this case, user means "programmer"; i.e., the user of the language itself, rather than the user of a program written in the language.
  2. There are actually several other native C++ types that we haven't used: long, float, double, and bool. The long type is useful for storing whole-number values that are larger than will fit into a short (hence the name), while float and double are able to store values that have fractional parts as well as integral values. These are useful in scientific and engineering calculations; I'll go into these types in somewhat more detail in Appendix . The bool type, recently added to C++, is useful for keeping track of a true/false condition. We'll see how to use this variable type later in this chapter.
  3. Please note that the terms class and storage class have nothing to do with one another. This is another case where C++ reuses the same word for different concepts.
  4. In case you were wondering, you can't create new native types.
  5. You might be wondering what the . in the statement soup.Display() means. It separates the name of the object, soup, from the function to be executed, Display. Hence, that statement could be translated as "perform the function Display on the object soup.
  6. By the way, in using a functional class such as StockItem to illustrate these concepts, I'm violating a venerable tradition in C++ tutorials. Normally, example classes represent zoo animals, or shapes, or something equally useful in common programming situations.
  7. For the implementation of the functions in this interface specification, see the following figures:

    a. For StockItem(), see Figure itemdefaultcons.

    b. For StockItem(string Name, short InStock, short Price, string Distributor, string UPC), see Figure itemnormalcons.

    c. For Display(), see Figure itemdisplay.

  8. By the way, public is a keyword in C++; that is, it is defined in the language. This means that you cannot have a function or variable called public.
  9. In case it isn't obvious how the compiler can figure out the size of the object, consider that the class definition specifies all of the variables that are used to implement the objects of the class; since the compiler already knows the definitions of all of these components, it can calculate the size of our class variables based on the sizes and types of those components. By the way, the size of our object isn't necessarily the sum of the sizes of its components; the compiler often has to add some space to the objects for reasons that are, unfortunately, beyond the scope of this book.
  10. By the way, spaces between components of the name aren't significant; that is, we can have them, as in Figure itemdefaultconsdecoder, or leave them out, as we did in Figure itemdefaultcons.
  11. Actually, I'm describing "normal" member variables here. There is another kind, which we won't be covering.
  12. Although scope rules and access specifiers are similar in some ways, in that they affect where a variable can be used, they aren't exactly the same. Scope defines where a variable is visible, whereas access specifiers control where a variable (or function) is accessible. That is, if you write a program that tries to read or modify a private variable from outside the class implementation, the compiler knows what you're trying to do but won't let you do it. On the other hand, if you try to access a local variable from a function where it isn't defined, the compiler just tells you it never heard of that variable, which indeed it hasn't in that context. For example, let's suppose that the local variable x defined in function abc has no existence in any other function; in that case, if you try to access a variable named x in another function, say def, where it hasn't been defined, you'll get an error message from the compiler telling you that there is no variable x in function def. However, if there is a private member variable called x defined in class ghi, and you try to access that member variable from a nonmember function, the compiler will tell you that you're trying to do something illegal. It knows which x you mean, but it won't let you access it because you don't have permission.
  13. The special status of member functions and variables as implementation aids explains why you cannot apply an access specifier such as public to data or functions declared outside a class, since the purpose of access specifiers is to control "outside" access to variables and functions used to implement a class. In case you were wondering, you can't apply access specifiers to native types, because the way they are implemented is not accessible to the programmer.
  14. Note that the names of the arguments are not part of the signature; in fact, you don't have to specify them in the function declaration at all. However, you should use meaningful argument names in the function declaration so that the user of the function has some idea what the arguments to the function might represent. After all, the declaration StockItem(string,short,short,string,string); doesn't provide much information on what its arguments actually mean.
  15. The break statement can also terminate execution of a while loop, as well as some other types of control mechanisms that we won't get to.
  16. The type bool is short for "boolean", which means "either true or false". The derivation of the term "boolean" is interesting but not relevant here.
  17. Note that the Found variable will be displayed as 0 for false and 1 for true, due to limitations of the debugger.
  18. As before, we can count on the compiler to supply the other three standard member functions needed for a concrete data type: the copy constructor, the assignment operator =, and the destructor.
  19. The reason it's only an approximate translation is that there is a special rule in C++ governing the execution of the || operator: if the expression on the left is true, then the expression on the right is not executed at all. The reason for this short-circuit evaluation rule is that in some cases you may want to write a right-hand expression that will only be legal if the left-hand expression is false.
  20. Purists may not approve of this use of the term object-oriented programming, as I'm not using this term in its strictest technical sense. However, since we are using objects and classes as our central organizing ideas, using the term object-oriented programming seems reasonable to me in this context.
  21. The compiler has also supplied a copy constructor for us, so that we can use StockItem objects as function arguments and return values. In this case, the compiler-generate copy constructor does exactly what we want, so we don't have to write our own. As we'll see in the next two chapters, these compiler-generated functions don't always behave properly, especially with more complicated classes than the StockItem class, where the compiler can't figure out how to copy or assign objects without more help from us.

Return to the table of contents